add handler logic to handle i2c pcie commands

Add handler logic to manage the i2c pcie commands and their
corresponding data structure.

Tested: Only ran unit-tests (added new ones).
Change-Id: Ibd65d6745202dbf6bd67cd2cb480914ca6ae4ed1
Signed-off-by: Patrick Venture <venture@google.com>
diff --git a/handler.cpp b/handler.cpp
index 041f77b..341e195 100644
--- a/handler.cpp
+++ b/handler.cpp
@@ -257,6 +257,22 @@
     return name;
 }
 
+void Handler::buildI2cPcieMapping()
+{
+    _pcie_i2c_map = buildPcieMap();
+}
+
+size_t Handler::getI2cPcieMappingSize() const
+{
+    return _pcie_i2c_map.size();
+}
+
+std::tuple<std::uint32_t, std::string>
+    Handler::getI2cEntry(unsigned int entry) const
+{
+    return _pcie_i2c_map[entry];
+}
+
 const std::string defaultConfigFile =
     "/usr/share/ipmi-entity-association/entity_association_map.json";
 
diff --git a/handler.hpp b/handler.hpp
index ec77657..ae84945 100644
--- a/handler.hpp
+++ b/handler.hpp
@@ -5,6 +5,7 @@
 #include <nlohmann/json.hpp>
 #include <string>
 #include <tuple>
+#include <vector>
 
 namespace google
 {
@@ -67,6 +68,27 @@
      */
     virtual std::string getEntityName(std::uint8_t id,
                                       std::uint8_t instance) = 0;
+
+    /**
+     * Populate the i2c-pcie mapping vector.
+     */
+    virtual void buildI2cPcieMapping() = 0;
+
+    /**
+     * Return the size of the i2c-pcie mapping vector.
+     *
+     * @return the size of the vector holding the i2c-pcie mapping tuples.
+     */
+    virtual size_t getI2cPcieMappingSize() const = 0;
+
+    /**
+     * Return a copy of the entry in the vector.
+     *
+     * @param[in] entry - the index into the vector.
+     * @return the tuple at that index.
+     */
+    virtual std::tuple<std::uint32_t, std::string>
+        getI2cEntry(unsigned int entry) const = 0;
 };
 
 class Handler : public HandlerInterface
@@ -81,6 +103,10 @@
     VersionTuple getCpldVersion(unsigned int id) const override;
     void psuResetDelay(std::uint32_t delay) const override;
     std::string getEntityName(std::uint8_t id, std::uint8_t instance) override;
+    void buildI2cPcieMapping() override;
+    size_t getI2cPcieMappingSize() const override;
+    std::tuple<std::uint32_t, std::string>
+        getI2cEntry(unsigned int entry) const override;
 
   private:
     std::string _configFile;
@@ -97,6 +123,8 @@
         {0x20, "memory_device"}};
 
     nlohmann::json _entityConfig{};
+
+    std::vector<std::tuple<uint32_t, std::string>> _pcie_i2c_map;
 };
 
 /**
diff --git a/pcie_i2c.cpp b/pcie_i2c.cpp
index 5f45650..07b04b9 100644
--- a/pcie_i2c.cpp
+++ b/pcie_i2c.cpp
@@ -30,17 +30,10 @@
 namespace ipmi
 {
 
-namespace
-{
-
 #ifndef MAX_IPMI_BUFFER
 #define MAX_IPMI_BUFFER 64
 #endif
 
-std::vector<std::tuple<uint32_t, std::string>> pcie_i2c_map;
-
-} // namespace
-
 struct PcieSlotCountRequest
 {
     uint8_t subcommand;
@@ -67,7 +60,7 @@
 } __attribute__((packed));
 
 ipmi_ret_t PcieSlotCount(const uint8_t* reqBuf, uint8_t* replyBuf,
-                         size_t* dataLen)
+                         size_t* dataLen, HandlerInterface* handler)
 {
     if ((*dataLen) < sizeof(struct PcieSlotCountRequest))
     {
@@ -77,12 +70,12 @@
     }
 
     // If there are already entries in the vector, clear them.
-    pcie_i2c_map = buildPcieMap();
+    handler->buildI2cPcieMapping();
 
     struct PcieSlotCountReply reply;
     reply.subcommand = SysPcieSlotCount;
     // Fill the pcie slot count as the number of entries in the vector.
-    reply.value = pcie_i2c_map.size();
+    reply.value = handler->getI2cPcieMappingSize();
 
     std::memcpy(&replyBuf[0], &reply, sizeof(reply));
 
@@ -93,7 +86,7 @@
 }
 
 ipmi_ret_t PcieSlotI2cBusMapping(const uint8_t* reqBuf, uint8_t* replyBuf,
-                                 size_t* dataLen)
+                                 size_t* dataLen, HandlerInterface* handler)
 {
     struct PcieSlotI2cBusMappingRequest request;
 
@@ -105,7 +98,8 @@
     }
 
     // If there are no entries in the vector return error.
-    if (pcie_i2c_map.empty())
+    size_t mapSize = handler->getI2cPcieMappingSize();
+    if (mapSize == 0)
     {
         return IPMI_CC_INVALID_RESERVATION_ID;
     }
@@ -114,14 +108,15 @@
 
     // The valid entries range from 0 to N - 1, N being the total number of
     // entries in the vector.
-    if (request.entry >= pcie_i2c_map.size())
+    if (request.entry >= mapSize)
     {
         return IPMI_CC_PARM_OUT_OF_RANGE;
     }
 
     // Get the i2c bus number and the pcie slot name from the vector.
-    uint32_t i2c_bus_number = std::get<0>(pcie_i2c_map[request.entry]);
-    std::string pcie_slot_name = std::get<1>(pcie_i2c_map[request.entry]);
+    auto i2cEntry = handler->getI2cEntry(request.entry);
+    uint32_t i2c_bus_number = std::get<0>(i2cEntry);
+    std::string pcie_slot_name = std::get<1>(i2cEntry);
 
     int length =
         sizeof(struct PcieSlotI2cBusMappingReply) + pcie_slot_name.length();
diff --git a/pcie_i2c.hpp b/pcie_i2c.hpp
index 3985698..fb4e6d3 100644
--- a/pcie_i2c.hpp
+++ b/pcie_i2c.hpp
@@ -1,5 +1,7 @@
 #pragma once
 
+#include "handler.hpp"
+
 #include <ipmid/api.h>
 
 namespace google
@@ -10,12 +12,14 @@
 //  Handle the pcie slot count command.
 //  Sys can query the number of pcie slots.
 ipmi_ret_t PcieSlotCount(const uint8_t* reqBuf, uint8_t* replyBuf,
-                         size_t* dataLen);
+                         size_t* dataLen,
+                         HandlerInterface* handler = &handlerImpl);
 
 // Handle the pcie slot to i2c bus mapping command.
 // Sys can query which i2c bus is routed to which pcie slot.
 ipmi_ret_t PcieSlotI2cBusMapping(const uint8_t* reqBuf, uint8_t* replyBuf,
-                                 size_t* dataLen);
+                                 size_t* dataLen,
+                                 HandlerInterface* handler = &handlerImpl);
 
 } // namespace ipmi
 } // namespace google
diff --git a/test/Makefile.am b/test/Makefile.am
index 0243d44..b9ad829 100644
--- a/test/Makefile.am
+++ b/test/Makefile.am
@@ -38,3 +38,7 @@
 check_PROGRAMS += entity_unittest
 entity_unittest_SOURCES = entity_unittest.cpp
 entity_unittest_LDADD = $(top_builddir)/libsyscmds_common.la
+
+check_PROGRAMS += pcie_unittest
+pcie_unittest_SOURCES = pcie_unittest.cpp
+pcie_unittest_LDADD = $(top_builddir)/libsyscmds_common.la
diff --git a/test/handler_mock.hpp b/test/handler_mock.hpp
index 6394f68..b97cce3 100644
--- a/test/handler_mock.hpp
+++ b/test/handler_mock.hpp
@@ -22,6 +22,10 @@
                                   std::uint8_t>(unsigned int));
     MOCK_CONST_METHOD1(psuResetDelay, void(std::uint32_t));
     MOCK_METHOD2(getEntityName, std::string(std::uint8_t, std::uint8_t));
+    MOCK_METHOD0(buildI2cPcieMapping, void());
+    MOCK_CONST_METHOD0(getI2cPcieMappingSize, size_t());
+    MOCK_CONST_METHOD1(getI2cEntry,
+                       std::tuple<std::uint32_t, std::string>(unsigned int));
 };
 
 } // namespace ipmi
diff --git a/test/pcie_unittest.cpp b/test/pcie_unittest.cpp
new file mode 100644
index 0000000..34926ee
--- /dev/null
+++ b/test/pcie_unittest.cpp
@@ -0,0 +1,99 @@
+#include "handler_mock.hpp"
+#include "main.hpp"
+#include "pcie_i2c.hpp"
+
+#include <cstdint>
+#include <cstring>
+#include <tuple>
+#include <vector>
+
+#include <gtest/gtest.h>
+
+#define MAX_IPMI_BUFFER 64
+
+using ::testing::Return;
+
+namespace google
+{
+namespace ipmi
+{
+
+TEST(PcieI2cCommandTest, PcieSlotCountTest)
+{
+    std::vector<std::uint8_t> request = {SysOEMCommands::SysPcieSlotCount};
+    size_t dataLen = request.size();
+    std::uint8_t reply[MAX_IPMI_BUFFER];
+    size_t expectedSize = 3;
+
+    HandlerMock hMock;
+    EXPECT_CALL(hMock, buildI2cPcieMapping());
+    EXPECT_CALL(hMock, getI2cPcieMappingSize()).WillOnce(Return(expectedSize));
+    EXPECT_EQ(IPMI_CC_OK,
+              PcieSlotCount(request.data(), reply, &dataLen, &hMock));
+    EXPECT_EQ(expectedSize, reply[1]);
+}
+
+TEST(PcieI2cCommandTest, PcieSlotEntryRequestTooShort)
+{
+    std::vector<std::uint8_t> request = {
+        SysOEMCommands::SysPcieSlotI2cBusMapping};
+    size_t dataLen = request.size();
+    std::uint8_t reply[MAX_IPMI_BUFFER];
+
+    HandlerMock hMock;
+    EXPECT_EQ(IPMI_CC_REQ_DATA_LEN_INVALID,
+              PcieSlotI2cBusMapping(request.data(), reply, &dataLen, &hMock));
+}
+
+TEST(PcieI2cCommandTest, PcieSlotEntryRequestUnsupportedByPlatform)
+{
+    // If there is no mapping in the device-tree, then the map is of size zero.
+    std::vector<std::uint8_t> request = {
+        SysOEMCommands::SysPcieSlotI2cBusMapping, 0};
+    size_t dataLen = request.size();
+    std::uint8_t reply[MAX_IPMI_BUFFER];
+
+    HandlerMock hMock;
+    EXPECT_CALL(hMock, getI2cPcieMappingSize()).WillOnce(Return(0));
+    EXPECT_EQ(IPMI_CC_INVALID_RESERVATION_ID,
+              PcieSlotI2cBusMapping(request.data(), reply, &dataLen, &hMock));
+}
+
+TEST(PcieI2cCommandTest, PcieSlotEntryRequestInvalidIndex)
+{
+    // index of 1 is invalid if length is 1.
+    std::vector<std::uint8_t> request = {
+        SysOEMCommands::SysPcieSlotI2cBusMapping, 1};
+    size_t dataLen = request.size();
+    std::uint8_t reply[MAX_IPMI_BUFFER];
+
+    HandlerMock hMock;
+    EXPECT_CALL(hMock, getI2cPcieMappingSize()).WillOnce(Return(1));
+    EXPECT_EQ(IPMI_CC_PARM_OUT_OF_RANGE,
+              PcieSlotI2cBusMapping(request.data(), reply, &dataLen, &hMock));
+}
+
+TEST(PcieI2cCommandTest, PcieSlotEntryRequestValidIndex)
+{
+    unsigned int index = 0;
+    std::vector<std::uint8_t> request = {
+        SysOEMCommands::SysPcieSlotI2cBusMapping,
+        static_cast<std::uint8_t>(index)};
+    size_t dataLen = request.size();
+    std::uint8_t reply[MAX_IPMI_BUFFER];
+    std::string slotName = "abcd";
+    std::uint32_t busNum = 5;
+
+    HandlerMock hMock;
+    EXPECT_CALL(hMock, getI2cPcieMappingSize()).WillOnce(Return(1));
+    EXPECT_CALL(hMock, getI2cEntry(index))
+        .WillOnce(Return(std::make_tuple(busNum, slotName)));
+    EXPECT_EQ(IPMI_CC_OK,
+              PcieSlotI2cBusMapping(request.data(), reply, &dataLen, &hMock));
+    EXPECT_EQ(busNum, reply[1]);
+    EXPECT_EQ(slotName.length(), reply[2]);
+    EXPECT_EQ(0, std::memcmp(slotName.c_str(), &reply[3], reply[2]));
+}
+
+} // namespace ipmi
+} // namespace google