PEL: PELTool Application

PELTooL application would be used to interact with PELs. This commit has
the first functionality, where a PEL file is passed and all PEL sections
are hexdumped in a JSON object.

Signed-off-by: Aatir <aatrapps@gmail.com>
Change-Id: I155d75bb58cbd14a297b094314f7fd1f271f4f37
diff --git a/extensions/openpower-pels/hexdump.cpp b/extensions/openpower-pels/hexdump.cpp
new file mode 100644
index 0000000..df9c650
--- /dev/null
+++ b/extensions/openpower-pels/hexdump.cpp
@@ -0,0 +1,121 @@
+#include "hexdump.hpp"
+
+#include <stdio.h>
+
+#include <cstring>
+#include <sstream>
+#include <string>
+
+namespace openpower
+{
+namespace pels
+{
+
+std::string escapeJSON(const std::string& input)
+{
+    std::string output;
+    output.reserve(input.length());
+
+    for (const auto c : input)
+    {
+        switch (c)
+        {
+            case '"':
+                output += "\\\"";
+                break;
+            case '/':
+                output += "\\/";
+                break;
+            case '\b':
+                output += "\\b";
+                break;
+            case '\f':
+                output += "\\f";
+                break;
+            case '\n':
+                output += "\\n";
+                break;
+            case '\r':
+                output += "\\r";
+                break;
+            case '\t':
+                output += "\\t";
+                break;
+            case '\\':
+                output += "\\\\";
+                break;
+            default:
+                output += c;
+                break;
+        }
+    }
+
+    return output;
+}
+char* dumpHex(const void* data, size_t size)
+{
+    const int symbolSize = 100;
+    char* buffer = (char*)calloc(10 * size, sizeof(char));
+    char* symbol = (char*)calloc(symbolSize, sizeof(char));
+    char ascii[17];
+    size_t i, j;
+    ascii[16] = '\0';
+    for (i = 0; i < size; ++i)
+    {
+        if (i % 16 == 0)
+        {
+            strcat(buffer, "\"");
+        }
+        snprintf(symbol, symbolSize, "%02X ", ((unsigned char*)data)[i]);
+        strcat(buffer, symbol);
+        memset(symbol, 0, strlen(symbol));
+        if (((unsigned char*)data)[i] >= ' ' &&
+            ((unsigned char*)data)[i] <= '~')
+        {
+            ascii[i % 16] = ((unsigned char*)data)[i];
+        }
+        else
+        {
+            ascii[i % 16] = '.';
+        }
+        if ((i + 1) % 8 == 0 || i + 1 == size)
+        {
+            std::string asciiString(ascii);
+            asciiString = escapeJSON(asciiString);
+            const char* asciiToPrint = asciiString.c_str();
+            strcat(buffer, " ");
+            if ((i + 1) % 16 == 0)
+            {
+                if (i + 1 != size)
+                {
+                    snprintf(symbol, symbolSize, "|  %s\", \n ", asciiToPrint);
+                }
+                else
+                {
+                    snprintf(symbol, symbolSize, "|  %s\" \n ", asciiToPrint);
+                }
+                strcat(buffer, symbol);
+                memset(symbol, 0, strlen(symbol));
+            }
+            else if (i + 1 == size)
+            {
+                ascii[(i + 1) % 16] = '\0';
+                if ((i + 1) % 16 <= 8)
+                {
+                    strcat(buffer, " ");
+                }
+                for (j = (i + 1) % 16; j < 16; ++j)
+                {
+                    strcat(buffer, "   ");
+                }
+                snprintf(symbol, symbolSize, "|  %s\" \n ", asciiToPrint);
+                strcat(buffer, symbol);
+                memset(symbol, 0, strlen(symbol));
+            }
+        }
+    }
+    free(symbol);
+    return buffer;
+}
+} // namespace pels
+} // namespace openpower
diff --git a/extensions/openpower-pels/hexdump.hpp b/extensions/openpower-pels/hexdump.hpp
new file mode 100644
index 0000000..bd26884
--- /dev/null
+++ b/extensions/openpower-pels/hexdump.hpp
@@ -0,0 +1,33 @@
+#pragma once
+
+#include <ctype.h>
+#include <stdio.h>
+
+#include <fstream>
+#include <iomanip>
+#include <iostream>
+#include <string>
+#include <vector>
+
+namespace openpower
+{
+namespace pels
+{
+
+/**
+ * @brief escape json - use it for PEL hex dumps.
+ * @param[in] std::string - the unescaped JSON as a string literal
+ * @return std::string - escaped JSON string literal
+ */
+std::string escapeJSON(const std::string& input);
+
+/**
+ * @brief get hex dump for PEL section in json format.
+ * @param[in] const void* - Raw PEL data
+ * @param[i] size_t - size of Raw PEL
+ * @return char * - the Hex dump
+ */
+char* dumpHex(const void* data, size_t size);
+
+} // namespace pels
+} // namespace openpower
diff --git a/extensions/openpower-pels/openpower-pels.mk b/extensions/openpower-pels/openpower-pels.mk
index 5099168..7690af4 100644
--- a/extensions/openpower-pels/openpower-pels.mk
+++ b/extensions/openpower-pels/openpower-pels.mk
@@ -8,6 +8,7 @@
 	extensions/openpower-pels/failing_mtms.cpp \
 	extensions/openpower-pels/fru_identity.cpp \
 	extensions/openpower-pels/generic.cpp \
+        extensions/openpower-pels/hexdump.cpp \
 	extensions/openpower-pels/log_id.cpp \
 	extensions/openpower-pels/manager.cpp \
 	extensions/openpower-pels/mru.cpp \
@@ -27,3 +28,47 @@
 
 registrydir = $(datadir)/phosphor-logging/pels/
 registry_DATA = extensions/openpower-pels/registry/message_registry.json
+
+bin_PROGRAMS += peltool
+
+peltool_SOURCES = extensions/openpower-pels/tools/peltool.cpp
+
+peltool_LDADD = \
+        extensions/openpower-pels/ascii_string.o \
+        extensions/openpower-pels/bcd_time.o \
+        extensions/openpower-pels/callout.o \
+        extensions/openpower-pels/callouts.o \
+        extensions/openpower-pels/failing_mtms.o \
+        extensions/openpower-pels/fru_identity.o \
+        extensions/openpower-pels/generic.o \
+        extensions/openpower-pels/hexdump.o \
+        extensions/openpower-pels/log_id.o \
+        extensions/openpower-pels/mru.o \
+        extensions/openpower-pels/mtms.o \
+        extensions/openpower-pels/paths.o \
+        extensions/openpower-pels/pce_identity.o \
+        extensions/openpower-pels/pel.o \
+        extensions/openpower-pels/pel_values.o \
+        extensions/openpower-pels/private_header.o \
+        extensions/openpower-pels/registry.o \
+        extensions/openpower-pels/repository.o \
+        extensions/openpower-pels/src.o \
+        extensions/openpower-pels/section_factory.o \
+        extensions/openpower-pels/severity.o \
+        extensions/openpower-pels/user_data.o \
+        extensions/openpower-pels/user_header.o
+
+
+peltool_LDFLAGS = \
+        $(SYSTEMD_LIBS) \
+        $(PHOSPHOR_LOGGING_LIBS) \
+        $(SDBUSPLUS_LIBS) \
+        $(PHOSPHOR_DBUS_INTERFACES_LIBS) \
+        $(SDEVENTPLUS_LIBS) \
+        -lstdc++fs
+
+peltool_CXXFLAGS = \
+        $(SYSTEMD_CFLAGS) \
+        $(SDBUSPLUS_CFLAGS) \
+        $(SDEVENTPLUS_CFLAGS) \
+        $(PHOSPHOR_DBUS_INTERFACES_CFLAGS)
diff --git a/extensions/openpower-pels/pel.cpp b/extensions/openpower-pels/pel.cpp
index 29d86d5..c4156a8 100644
--- a/extensions/openpower-pels/pel.cpp
+++ b/extensions/openpower-pels/pel.cpp
@@ -2,12 +2,15 @@
 
 #include "bcd_time.hpp"
 #include "failing_mtms.hpp"
+#include "hexdump.hpp"
 #include "log_id.hpp"
+#include "pel_values.hpp"
 #include "section_factory.hpp"
 #include "src.hpp"
 #include "stream.hpp"
 #include "user_data_formats.hpp"
 
+#include <iostream>
 #include <phosphor-logging/log.hpp>
 
 namespace openpower
@@ -15,6 +18,7 @@
 namespace pels
 {
 namespace message = openpower::pels::message;
+namespace pv = openpower::pels::pel_values;
 
 PEL::PEL(const message::Entry& entry, uint32_t obmcLogID, uint64_t timestamp,
          phosphor::logging::Entry::Level severity,
@@ -171,5 +175,45 @@
 }
 
 } // namespace util
+void PEL::printSectionInJSON(Section& section, std::string& buf) const
+{
+    char tmpB[5];
+    if (section.valid())
+    {
+        uint8_t id[] = {static_cast<uint8_t>(section.header().id >> 8),
+                        static_cast<uint8_t>(section.header().id)};
+        sprintf(tmpB, "%c%c", id[0], id[1]);
+        std::string sectionID(tmpB);
+        std::string sectionName = pv::sectionTitles.count(sectionID)
+                                      ? pv::sectionTitles.at(sectionID)
+                                      : "Unknown Section";
+        buf += "\n\"" + sectionName + "\":[\n ";
+        std::vector<uint8_t> data;
+        Stream s{data};
+        section.flatten(s);
+        std::string dstr = dumpHex(std::data(data), data.size());
+        buf += dstr + "\n],\n";
+    }
+    else
+    {
+        buf += "\n\"Invalid Section  \":[\n invalid \n],\n";
+    }
+}
+
+void PEL::toJSON()
+{
+    std::string buf = "{";
+    printSectionInJSON(*(_ph.get()), buf);
+    printSectionInJSON(*(_uh.get()), buf);
+    for (auto& section : this->optionalSections())
+    {
+        printSectionInJSON(*(section.get()), buf);
+    }
+    buf += "}";
+    std::size_t found = buf.rfind(",");
+    if (found != std::string::npos)
+        buf.replace(found, 1, "");
+    std::cout << buf << std::endl;
+}
 } // namespace pels
 } // namespace openpower
diff --git a/extensions/openpower-pels/pel.hpp b/extensions/openpower-pels/pel.hpp
index 3d1318f..3498ae6 100644
--- a/extensions/openpower-pels/pel.hpp
+++ b/extensions/openpower-pels/pel.hpp
@@ -214,6 +214,11 @@
      */
     void assignID();
 
+    /**
+     * @brief Output a PEL in JSON.
+     */
+    void toJSON();
+
   private:
     /**
      * @brief Builds the section objects from a PEL data buffer
@@ -248,6 +253,13 @@
      * @brief Holds all sections by the PH and UH.
      */
     std::vector<std::unique_ptr<Section>> _optionalSections;
+
+    /**
+     * @brief helper function for printing PELs.
+     * @param[in] Section& - section object reference
+     * @param[in] std::string - PEL string
+     */
+    void printSectionInJSON(Section& section, std::string& buf) const;
 };
 
 namespace util
diff --git a/extensions/openpower-pels/pel_values.cpp b/extensions/openpower-pels/pel_values.cpp
index a3127fb..0cf2a1c 100644
--- a/extensions/openpower-pels/pel_values.cpp
+++ b/extensions/openpower-pels/pel_values.cpp
@@ -190,7 +190,31 @@
                         });
 }
 
-} // namespace pel_values
+/**
+ * @brief Map for section IDs
+ */
+const std::map<std::string, std::string> sectionTitles = {
 
+    {"PH", "Private Header"},
+    {"UH", "User Header"},
+    {"PS", "Primary SRC"},
+    {"SS", "Secondary SRC"},
+    {"EH", "Extended User Header"},
+    {"MT", "Failing MTMS"},
+    {"DH", "Dump Location"},
+    {"SW", "Firmware Error"},
+    {"LP", "Impacted Part"},
+    {"LR", "Logical Resource"},
+    {"HM", "HMC ID"},
+    {"EP", "EPOW"},
+    {"IE", "IO Event"},
+    {"MI", "MFG Info"},
+    {"CH", "Call Home"},
+    {"UD", "User Data"},
+    {"EI", "Env Info"},
+    {"ED", "Extended User Data"},
+};
+
+} // namespace pel_values
 } // namespace pels
 } // namespace openpower
diff --git a/extensions/openpower-pels/pel_values.hpp b/extensions/openpower-pels/pel_values.hpp
index 6092f62..49aa374 100644
--- a/extensions/openpower-pels/pel_values.hpp
+++ b/extensions/openpower-pels/pel_values.hpp
@@ -1,5 +1,6 @@
 #pragma once
 
+#include <map>
 #include <string>
 #include <tuple>
 #include <vector>
@@ -76,6 +77,11 @@
  */
 extern const PELValues calloutPriorityValues;
 
+/**
+ * @brief Map for section IDs
+ */
+extern const std::map<std::string, std::string> sectionTitles;
+
 } // namespace pel_values
 } // namespace pels
 } // namespace openpower
diff --git a/extensions/openpower-pels/tools/peltool.cpp b/extensions/openpower-pels/tools/peltool.cpp
new file mode 100644
index 0000000..f682a42
--- /dev/null
+++ b/extensions/openpower-pels/tools/peltool.cpp
@@ -0,0 +1,64 @@
+#include "../pel.hpp"
+
+#include <CLI/CLI.hpp>
+#include <iostream>
+#include <string>
+
+using namespace phosphor::logging;
+using namespace openpower::pels;
+
+/**
+ * @brief get data form raw PEL file.
+ * @param[in] std::string Name of file with raw PEL
+ * @return std::vector<uint8_t> char vector read from raw PEL file.
+ */
+std::vector<uint8_t> getFileData(std::string name)
+{
+    std::ifstream file(name, std::ifstream::in);
+    if (file.good())
+    {
+        std::vector<uint8_t> data{std::istreambuf_iterator<char>(file),
+                                  std::istreambuf_iterator<char>()};
+        return data;
+    }
+    else
+    {
+        printf("Can't open raw PEL file");
+        return {};
+    }
+}
+
+static void exitWithError(const std::string& help, const char* err)
+{
+    std::cerr << "ERROR: " << err << std::endl << help << std::endl;
+    exit(-1);
+}
+
+int main(int argc, char** argv)
+{
+    CLI::App app{"OpenBMC PEL Tool"};
+    std::string fileName;
+    app.add_option("-f,--file", fileName, "Raw PEL File");
+    CLI11_PARSE(app, argc, argv);
+
+    if (!fileName.empty())
+    {
+        std::vector<uint8_t> data = getFileData(fileName);
+        if (!data.empty())
+        {
+            PEL pel{data};
+            pel.toJSON();
+        }
+        else
+        {
+            exitWithError(app.help("", CLI::AppFormatMode::All),
+                          "Raw PEL file can't be read.");
+        }
+    }
+    else
+    {
+        exitWithError(app.help("", CLI::AppFormatMode::All),
+                      "Raw PEL file path not specified.");
+    }
+    return 0;
+}
diff --git a/test/openpower-pels/Makefile.include b/test/openpower-pels/Makefile.include
index d110cc1..312a7e0 100644
--- a/test/openpower-pels/Makefile.include
+++ b/test/openpower-pels/Makefile.include
@@ -35,6 +35,7 @@
 	$(top_builddir)/extensions/openpower-pels/failing_mtms.o \
 	$(top_builddir)/extensions/openpower-pels/fru_identity.o \
 	$(top_builddir)/extensions/openpower-pels/generic.o \
+        $(top_builddir)/extensions/openpower-pels/hexdump.o \
 	$(top_builddir)/extensions/openpower-pels/log_id.o \
 	$(top_builddir)/extensions/openpower-pels/mtms.o \
 	$(top_builddir)/extensions/openpower-pels/mru.o \