PDR: implement generation framework

This commit implements a framework that allows describing
platform/system specific PDR information in JSON files, which the PLDM
responder library parses and builds a PDR out of.

Separate JSON files are expected per PDR type. This commit also adds the
code to build a state effecter PDR. PDR record handles are implemented
as incrementing indices, starting at 1, but this commit enables
implementing other type of record handles (for eg offset based).

This commit doesn't handle merging of PDRs received from other terminii,
so the PDR that's generated now is for the BMC. Merging of PDRs will be
implemented in future commits.

Change-Id: I005b08c5d29d12fc2459ca8d6e18547107a3da7d
Signed-off-by: Deepak Kodihalli <dkodihal@in.ibm.com>
diff --git a/libpldmresponder/Makefile.am b/libpldmresponder/Makefile.am
index 21cfdc8..0bd1ea5 100644
--- a/libpldmresponder/Makefile.am
+++ b/libpldmresponder/Makefile.am
@@ -3,16 +3,24 @@
 libpldmresponder_la_SOURCES = \
 	base.cpp \
 	utils.cpp \
-	bios.cpp
+	bios.cpp \
+	effecters.cpp \
+	pdr.cpp
 libpldmresponder_la_LIBADD = \
 	../libpldm/libpldm.la \
 	$(CODE_COVERAGE_LIBS)
 libpldmresponder_la_LDFLAGS = \
 	$(SDBUSPLUS_LIBS) \
+	$(PHOSPHOR_LOGGING_LIBS) \
+	$(PHOSPHOR_DBUS_INTERFACES_LIBS) \
+	-lstdc++fs \
 	-version-info 1:0:0 -shared
 libpldmresponder_la_CXXFLAGS = \
 	$(CODE_COVERAGE_CXXFLAGS) \
-	$(SDBUSPLUS_CFLAGS)
+	$(SDBUSPLUS_CFLAGS) \
+	$(PHOSPHOR_LOGGING_CFLAGS) \
+	$(PHOSPHOR_DBUS_INTERFACES_CFLAGS)
+
 libpldmresponder_la_CPPFLAGS = \
 	$(CODE_COVERAGE_CPPFLAGS) \
    	-I$(top_builddir)/libpldm/ \
diff --git a/libpldmresponder/effecters.cpp b/libpldmresponder/effecters.cpp
new file mode 100644
index 0000000..ecc63bc
--- /dev/null
+++ b/libpldmresponder/effecters.cpp
@@ -0,0 +1,20 @@
+#include "effecters.hpp"
+
+namespace pldm
+{
+
+namespace responder
+{
+
+namespace effecter
+{
+
+Id nextId()
+{
+    static Id id = 0;
+    return ++id;
+}
+
+} // namespace effecter
+} // namespace responder
+} // namespace pldm
diff --git a/libpldmresponder/effecters.hpp b/libpldmresponder/effecters.hpp
new file mode 100644
index 0000000..a0704df
--- /dev/null
+++ b/libpldmresponder/effecters.hpp
@@ -0,0 +1,24 @@
+#pragma once
+
+#include <stdint.h>
+
+namespace pldm
+{
+
+namespace responder
+{
+
+namespace effecter
+{
+
+using Id = uint16_t;
+
+/** @brief Get next available id to assign to an effecter
+ *
+ *  @return  uint16_t - effecter id
+ */
+Id nextId();
+
+} // namespace effecter
+} // namespace responder
+} // namespace pldm
diff --git a/libpldmresponder/examples/pdr/11.json b/libpldmresponder/examples/pdr/11.json
new file mode 100644
index 0000000..7410ab9
--- /dev/null
+++ b/libpldmresponder/examples/pdr/11.json
@@ -0,0 +1,33 @@
+{
+    "entries" : [{
+        "type" : 33,
+        "instance" : 0,
+        "container" : 0,
+        "effecters" : [{
+            "set" : {
+                "id" : 196,
+                "size" : 1,
+                "states" : [1]
+            }
+        }]
+    },
+    {
+        "type" : 100,
+        "instance" : 0,
+        "container" : 0,
+        "effecters" : [{
+            "set" : {
+                "id" : 197,
+                "size" : 1,
+                "states" : [1]
+            }
+        },
+        {
+            "set" : {
+                "id" : 198,
+                "size" : 2,
+                "states" : [1,2,5,15]
+            }
+        }]
+    }]
+}
diff --git a/libpldmresponder/pdr.cpp b/libpldmresponder/pdr.cpp
new file mode 100644
index 0000000..e252cb3
--- /dev/null
+++ b/libpldmresponder/pdr.cpp
@@ -0,0 +1,26 @@
+#include "pdr.hpp"
+
+namespace pldm
+{
+
+namespace responder
+{
+
+namespace pdr
+{
+
+Repo& get(const std::string& dir)
+{
+    using namespace internal;
+    static IndexedRepo repo;
+    if (repo.empty())
+    {
+        generate(dir, repo);
+    }
+
+    return repo;
+}
+
+} // namespace pdr
+} // namespace responder
+} // namespace pldm
diff --git a/libpldmresponder/pdr.hpp b/libpldmresponder/pdr.hpp
new file mode 100644
index 0000000..9cbfbac
--- /dev/null
+++ b/libpldmresponder/pdr.hpp
@@ -0,0 +1,312 @@
+#pragma once
+
+#include "effecters.hpp"
+
+#include <stdint.h>
+
+#include <filesystem>
+#include <fstream>
+#include <functional>
+#include <map>
+#include <nlohmann/json.hpp>
+#include <phosphor-logging/elog-errors.hpp>
+#include <phosphor-logging/log.hpp>
+#include <string>
+#include <vector>
+#include <xyz/openbmc_project/Common/error.hpp>
+
+#include "libpldm/platform.h"
+
+using namespace phosphor::logging;
+using InternalFailure =
+    sdbusplus::xyz::openbmc_project::Common::Error::InternalFailure;
+namespace fs = std::filesystem;
+
+namespace pldm
+{
+
+namespace responder
+{
+
+namespace pdr
+{
+
+using Type = uint8_t;
+using Json = nlohmann::json;
+using RecordHandle = uint32_t;
+using Entry = std::vector<uint8_t>;
+using Pdr = std::vector<Entry>;
+
+/** @class Repo
+ *
+ *  @brief Abstract class describing the interface API to the PDR repository
+ *
+ *  Concrete implementations of this must handle storing and addressing the
+ *  PDR entries by a "record handle", which can be indices, offsets, etc.
+ */
+class Repo
+{
+  public:
+    /** @brief Add a new entry to the PDR
+     *
+     *  @param[in] entry - new PDR entry
+     */
+    virtual void add(Entry&& entry) = 0;
+
+    /** @brief Access PDR entry at inout record handle
+     *
+     *  @param[in] handle - record handle
+     *
+     *  @return Entry - PDR entry
+     */
+    virtual Entry at(RecordHandle handle) const = 0;
+
+    /** @brief Get next available record handle for assignment
+     *
+     *  @return RecordHandle - PDR record handle
+     */
+    virtual RecordHandle getNextRecordHandle() const = 0;
+
+    /** @brief Get record handle immediately suceeding the input record
+     *         handle
+     *
+     *  @param[in] current - input record handle
+     *
+     *  @return RecordHandle - PDR record handle
+     */
+    virtual RecordHandle getNextRecordHandle(RecordHandle current) const = 0;
+
+    /** @brief Get number of entries in the PDR
+     *
+     *  @return size_t - number of entries
+     */
+    virtual size_t numEntries() const = 0;
+
+    /** @brief Check if PDR is empty
+     *
+     *  @return bool - true if PDR is empty, false otherwise
+     */
+    virtual bool empty() const = 0;
+
+    /** @brief Empty the PDR
+     */
+    virtual void makeEmpty() = 0;
+};
+
+namespace internal
+{
+
+/** @brief Parse PDR JSON file and output Json object
+ *
+ *  @param[in] path - path of PDR JSON file
+ *
+ *  @return Json - Json object
+ */
+inline Json readJson(const std::string& path)
+{
+    std::ifstream jsonFile(path);
+    if (!jsonFile.is_open())
+    {
+        log<level::INFO>("Error opening PDR JSON file",
+                         entry("PATH=%s", path.c_str()));
+        return {};
+    }
+
+    return Json::parse(jsonFile);
+}
+
+/** @class IndexedRepo
+ *
+ *  @brief Inherits and implements Repo
+ *
+ *  Stores the PDR as a vector of entries, and addresses PDR entries based on an
+ *  incrementing record handle, starting at 1.
+ */
+class IndexedRepo : public Repo
+{
+  public:
+    void add(Entry&& entry)
+    {
+        repo.emplace_back(std::move(entry));
+    }
+
+    Entry at(RecordHandle handle) const
+    {
+        if (!handle)
+        {
+            handle = 1;
+        }
+        return repo.at(handle - 1);
+    }
+
+    RecordHandle getNextRecordHandle() const
+    {
+        return repo.size() + 1;
+    }
+
+    RecordHandle getNextRecordHandle(RecordHandle current) const
+    {
+        if (current >= repo.size())
+        {
+            return 0;
+        }
+        if (!current)
+        {
+            current = 1;
+        }
+        return current + 1;
+    }
+
+    size_t numEntries() const
+    {
+        return repo.size();
+    }
+
+    bool empty() const
+    {
+        return repo.empty();
+    }
+
+    void makeEmpty()
+    {
+        repo.clear();
+    }
+
+  private:
+    Pdr repo{};
+};
+
+/** @brief Parse PDR JSONs and build PDR repository
+ *
+ *  @param[in] dir - directory housing platform specific PDR JSON files
+ *  @tparam[in] repo - instance of concrete implementation of Repo
+ */
+template <typename T>
+void generate(const std::string& dir, T& repo)
+{
+    using namespace internal;
+    // A map of PDR type to a lambda that handles creation of that PDR type.
+    // The lambda essentially would parse the platform specific PDR JSONs to
+    // generate the PDR structures. This function iterates through the map to
+    // invoke all lambdas, so that all PDR types can be created.
+    std::map<Type, std::function<void(const Json& json, T& repo)>> generators =
+        {{PLDM_STATE_EFFECTER_PDR, [](const auto& json, T& repo) {
+              static const std::vector<Json> emptyList{};
+              static const Json empty{};
+              auto entries = json.value("entries", emptyList);
+              for (const auto& e : entries)
+              {
+                  size_t pdrSize = 0;
+                  auto effecters = e.value("effecters", emptyList);
+                  static const Json empty{};
+                  for (const auto& effecter : effecters)
+                  {
+                      auto set = effecter.value("set", empty);
+                      auto statesSize = set.value("size", 0);
+                      if (!statesSize)
+                      {
+                          log<level::ERR>(
+                              "Malformed PDR JSON - no state set info",
+                              entry("TYPE=%d", PLDM_STATE_EFFECTER_PDR));
+                          elog<InternalFailure>();
+                      }
+                      pdrSize += sizeof(state_effecter_possible_states) -
+                                 sizeof(bitfield8_t) +
+                                 (sizeof(bitfield8_t) * statesSize);
+                  }
+                  pdrSize += sizeof(pldm_state_effecter_pdr) - sizeof(uint8_t);
+
+                  Entry pdrEntry{};
+                  pdrEntry.resize(pdrSize);
+
+                  pldm_state_effecter_pdr* pdr =
+                      reinterpret_cast<pldm_state_effecter_pdr*>(
+                          pdrEntry.data());
+                  pdr->hdr.record_handle = repo.getNextRecordHandle();
+                  pdr->hdr.version = 1;
+                  pdr->hdr.type = PLDM_STATE_EFFECTER_PDR;
+                  pdr->hdr.record_change_num = 0;
+                  pdr->hdr.length = pdrSize - sizeof(pldm_pdr_hdr);
+
+                  pdr->terminus_handle = 0;
+                  pdr->effecter_id = effecter::nextId();
+                  pdr->entity_type = e.value("type", 0);
+                  pdr->entity_instance = e.value("instance", 0);
+                  pdr->container_id = e.value("container", 0);
+                  pdr->effecter_semantic_id = 0;
+                  pdr->effecter_init = PLDM_NO_INIT;
+                  pdr->has_description_pdr = false;
+                  pdr->composite_effecter_count = effecters.size();
+
+                  uint8_t* start = pdrEntry.data() +
+                                   sizeof(pldm_state_effecter_pdr) -
+                                   sizeof(uint8_t);
+                  for (const auto& effecter : effecters)
+                  {
+                      auto set = effecter.value("set", empty);
+                      state_effecter_possible_states* possibleStates =
+                          reinterpret_cast<state_effecter_possible_states*>(
+                              start);
+                      possibleStates->state_set_id = set.value("id", 0);
+                      possibleStates->possible_states_size =
+                          set.value("size", 0);
+
+                      start += sizeof(possibleStates->state_set_id) +
+                               sizeof(possibleStates->possible_states_size);
+                      static const std::vector<uint8_t> emptyStates{};
+                      auto states = set.value("states", emptyStates);
+                      for (const auto& state : states)
+                      {
+                          auto index = state / 8;
+                          auto bit = state - (index * 8);
+                          bitfield8_t* bf =
+                              reinterpret_cast<bitfield8_t*>(start + index);
+                          bf->byte |= 1 << bit;
+                      }
+                      start += possibleStates->possible_states_size;
+                  }
+                  repo.add(std::move(pdrEntry));
+              }
+          }}};
+
+    auto eraseLen = strlen(".json");
+    Type pdrType{};
+    for (const auto& dirEntry : fs::directory_iterator(dir))
+    {
+        try
+        {
+            auto json = readJson(dirEntry.path().string());
+            if (!json.empty())
+            {
+                auto fileName = dirEntry.path().filename().string();
+                fileName.erase(fileName.end() - eraseLen);
+                pdrType = stoi(fileName);
+                generators.at(pdrType)(json, repo);
+            }
+        }
+        catch (const InternalFailure& e)
+        {
+        }
+        catch (const std::exception& e)
+        {
+            log<level::ERR>("Failed parsing PDR JSON file",
+                            entry("TYPE=%d", pdrType),
+                            entry("ERROR=%s", e.what()));
+            report<InternalFailure>();
+        }
+    }
+}
+
+} // namespace internal
+
+/** @brief Build (if not built already) and retrieve PDR
+ *
+ *  @param[in] dir - directory housing platform specific PDR JSON files
+ *
+ *  @return Repo& - Reference to instance of pdr::Repo
+ */
+Repo& get(const std::string& dir);
+
+} // namespace pdr
+} // namespace responder
+} // namespace pldm