Log PEL and dump VPD if invalid record(s) found

This commit adds changes in ipz parser to log predictive PEL if any
invalid record(s) is found while parsing. Processing of records still
continues even if any invalid record is found. At the end of parsing the
VPD:
1. A predictive PEL is logged which specifies FRU path and list of
   invalid records.
2. The bad VPD binary data is dumped to filesystem so that it can be
   collected by FFDC.

Test:
```
1. Using dd command, zero out VINI DR and VMPU VZ  keyword values of Op
   Panel EEPROM(/sys/bus/i2c/drivers/at24/7-0051/eeprom).
2. Restart vpd-manager service.
3. Check PEL list using peltool and observe that a predictive PEL is
   logged with the Op Panel EEPROM path in the description and a list of
   bad record names and respective error(Data/ECC) in the UserData1
   section.
4. Check /var/lib/vpd/ directory and see dumps directory is created with
   a dump file which contains the Op Panel EEPROM binary data
5. Restore the Op Panel EEPROM with backed up correct data
6. Clear all PELs using peltool
7. Restart vpd-manager
8. Observe no new PELs logged for invalid records
```

Change-Id: Iebb526bae3c467b1cd0a256c93d57a56fb87e9f6
Signed-off-by: Souvik Roy <souvikroyofficial10@gmail.com>
diff --git a/vpd-manager/include/event_logger.hpp b/vpd-manager/include/event_logger.hpp
index aec8d30..65399e9 100644
--- a/vpd-manager/include/event_logger.hpp
+++ b/vpd-manager/include/event_logger.hpp
@@ -41,6 +41,17 @@
     static std::string getErrorMsg(const std::exception& i_exception);
 
     /**
+     * @brief API to get string representation of a Error type enum.
+     *
+     * @param[in] i_exception - Exception object.
+     *
+     * @return Error msg set for the specific error type. Default error msg
+     * otherwise.
+     */
+    static std::string getErrorTypeString(
+        const types::ErrorType& i_errorType) noexcept;
+
+    /**
      * @brief An API to create a PEL with inventory path callout.
      *
      * This API calls an async method to create PEL, and also handles inventory
diff --git a/vpd-manager/include/ipz_parser.hpp b/vpd-manager/include/ipz_parser.hpp
index b41b7b6..61100e9 100644
--- a/vpd-manager/include/ipz_parser.hpp
+++ b/vpd-manager/include/ipz_parser.hpp
@@ -152,10 +152,11 @@
      *
      * @param[in] itrToPT - Iterator to PT record in VPD vector.
      * @param[in] ptLength - length of the PT record.
-     * @return List of record's offset.
+     * @return Pair of list of record's offset and a list of invalid
+     * records found during parsing
      */
-    types::RecordOffsetList readPT(types::BinaryVector::const_iterator& itrToPT,
-                                   auto ptLength);
+    std::pair<types::RecordOffsetList, types::InvalidRecordList> readPT(
+        types::BinaryVector::const_iterator& itrToPT, auto ptLength);
 
     /**
      * @brief API to read keyword data based on its encoding type.
@@ -255,6 +256,20 @@
                                 const types::RecordOffset& i_recordDataOffset,
                                 types::BinaryVector& io_vpdVector);
 
+    /**
+     * @brief API to process list of invalid records found during parsing
+     *
+     * This API takes a list of invalid records found while parsing a given
+     * EEPROM, logs a predictive PEL with details about the invalid records and
+     * then dumps the EEPROM data to filesystem.
+     *
+     * @param[in] i_invalidRecordList - a list of invalid records
+     *
+     * @return On success returns true, false otherwise.
+     */
+    bool processInvalidRecords(
+        const types::InvalidRecordList& i_invalidRecordList) const noexcept;
+
     // Holds VPD data.
     const types::BinaryVector& m_vpdVector;
 
diff --git a/vpd-manager/include/types.hpp b/vpd-manager/include/types.hpp
index 09153f4..cffab81 100644
--- a/vpd-manager/include/types.hpp
+++ b/vpd-manager/include/types.hpp
@@ -198,5 +198,10 @@
 using ExceptionInfoVariant = std::variant<std::monostate, ErrorType, std::string>;
 /* Error info map of format <Error format, Value> */
 using ExceptionDataMap = std::map<std::string, ExceptionInfoVariant>;
+
+/* Pair of invalid record name and error encountered while parsing the record*/
+using InvalidRecordEntry = std::pair<Record,ErrorType>;
+/* List of invalid record entries*/
+using InvalidRecordList = std::vector<InvalidRecordEntry>;
 } // namespace types
 } // namespace vpd
diff --git a/vpd-manager/src/event_logger.cpp b/vpd-manager/src/event_logger.cpp
index d5bf3d2..a114df5 100644
--- a/vpd-manager/src/event_logger.cpp
+++ b/vpd-manager/src/event_logger.cpp
@@ -423,4 +423,13 @@
 
     return *l_ptrToErrMsg;
 }
+
+std::string EventLogger::getErrorTypeString(
+    const types::ErrorType& i_errorType) noexcept
+{
+    const auto l_entry = m_errorMsgMap.find(i_errorType);
+    return (l_entry != m_errorMsgMap.end()
+                ? l_entry->second
+                : m_errorMsgMap.at(types::ErrorType::UndefinedError));
+}
 } // namespace vpd
diff --git a/vpd-manager/src/ipz_parser.cpp b/vpd-manager/src/ipz_parser.cpp
index a465a03..828c17c 100644
--- a/vpd-manager/src/ipz_parser.cpp
+++ b/vpd-manager/src/ipz_parser.cpp
@@ -7,6 +7,7 @@
 #include "constants.hpp"
 #include "event_logger.hpp"
 #include "exceptions.hpp"
+#include "utility/vpd_specific_utility.hpp"
 
 #include <nlohmann/json.hpp>
 
@@ -242,11 +243,15 @@
     return ptLen;
 }
 
-types::RecordOffsetList IpzVpdParser::readPT(
-    types::BinaryVector::const_iterator& itrToPT, auto ptLength)
+std::pair<types::RecordOffsetList, types::InvalidRecordList>
+    IpzVpdParser::readPT(types::BinaryVector::const_iterator& itrToPT,
+                         auto ptLength)
 {
     types::RecordOffsetList recordOffsets;
 
+    // List of names of all invalid records found.
+    types::InvalidRecordList l_invalidRecordList;
+
     auto end = itrToPT;
     std::advance(end, ptLength);
 
@@ -268,35 +273,13 @@
                 throw(EccException("ERROR: ECC check failed"));
             }
         }
-        catch (const EccException& ex)
+        catch (const std::exception& l_ex)
         {
-            logging::logMessage(ex.what());
+            logging::logMessage(l_ex.what());
 
-            /*TODO: uncomment when PEL code goes in */
-
-            /*std::string errMsg =
-                std::string{ex.what()} + " Record: " + recordName;
-
-            inventory::PelAdditionalData additionalData{};
-            additionalData.emplace("DESCRIPTION", errMsg);
-            additionalData.emplace("CALLOUT_INVENTORY_PATH", inventoryPath);
-            createPEL(additionalData, PelSeverity::WARNING,
-                      errIntfForEccCheckFail, nullptr);*/
-        }
-        catch (const DataException& ex)
-        {
-            logging::logMessage(ex.what());
-
-            /*TODO: uncomment when PEL code goes in */
-
-            /*std::string errMsg =
-                std::string{ex.what()} + " Record: " + recordName;
-
-            inventory::PelAdditionalData additionalData{};
-            additionalData.emplace("DESCRIPTION", errMsg);
-            additionalData.emplace("CALLOUT_INVENTORY_PATH", inventoryPath);
-            createPEL(additionalData, PelSeverity::WARNING,
-                      errIntfForInvalidVPD, nullptr);*/
+            // add the invalid record name and exception object to list
+            l_invalidRecordList.emplace_back(types::InvalidRecordEntry{
+                recordName, EventLogger::getErrorType(l_ex)});
         }
 
         // Jump record size, record length, ECC offset and ECC length
@@ -305,7 +288,7 @@
                          sizeof(types::ECCOffset) + sizeof(types::ECCLength));
     }
 
-    return recordOffsets;
+    return std::make_pair(recordOffsets, l_invalidRecordList);
 }
 
 types::IPZVpdMap::mapped_type IpzVpdParser::readKeywords(
@@ -403,12 +386,19 @@
 
         // Read the table of contents record, to get offsets
         // to other records.
-        auto recordOffsets = readPT(itrToVPD, ptLen);
+        auto l_result = readPT(itrToVPD, ptLen);
+        auto recordOffsets = l_result.first;
         for (const auto& offset : recordOffsets)
         {
             processRecord(offset);
         }
 
+        if (!processInvalidRecords(l_result.second))
+        {
+            logging::logMessage("Failed to process invalid records for [" +
+                                m_vpdFilePath + "]");
+        }
+
         return m_parsedVPDMap;
     }
     catch (const std::exception& e)
@@ -821,4 +811,52 @@
 
     return l_sizeWritten;
 }
+
+bool IpzVpdParser::processInvalidRecords(
+    const types::InvalidRecordList& i_invalidRecordList) const noexcept
+{
+    bool l_rc{true};
+    if (!i_invalidRecordList.empty())
+    {
+        auto l_invalidRecordToString =
+            [](const types::InvalidRecordEntry& l_record) {
+                return std::string{
+                    "{" + l_record.first + "," +
+                    EventLogger::getErrorTypeString(l_record.second) + "}"};
+            };
+
+        std::string l_invalidRecordListString{"["};
+        try
+        {
+            for (const auto& l_entry : i_invalidRecordList)
+            {
+                l_invalidRecordListString +=
+                    l_invalidRecordToString(l_entry) + ",";
+            }
+            l_invalidRecordListString += "]";
+        }
+        catch (const std::exception& l_ex)
+        {
+            l_invalidRecordListString = "";
+        }
+
+        // Log a Predictive PEL, including names and respective error messages
+        // of all invalid records
+        EventLogger::createSyncPel(
+            types::ErrorType::InvalidVpdMessage, types::SeverityType::Warning,
+            __FILE__, __FUNCTION__, constants::VALUE_0,
+            std::string("Invalid records found while parsing VPD for [" +
+                        m_vpdFilePath + "]"),
+            l_invalidRecordListString, std::nullopt, std::nullopt,
+            std::nullopt);
+
+        // Dump Bad VPD to file
+        if (constants::SUCCESS !=
+            vpdSpecificUtility::dumpBadVpd(m_vpdFilePath, m_vpdVector))
+        {
+            l_rc = false;
+        }
+    }
+    return l_rc;
+}
 } // namespace vpd