event: Handling pldmPDRRepositoryChgEvent

Added a handler to react to the "pldmPDRRepositoryChgEvent" event and
extract the data. If the eventDataFormat is "formatIsPDRHandles" then
the changeRecords are further extracted. If the eventDataOperation is
"recordAdded" then the PDRHandles are extracted from the changeEntry
data and stored in the "pdrRecordHandle" vector.

"decode_pldm_pdr_repository_chg_event_data" function will extract the
eventData and changeRecord from the pldmPDRRepositoryChgEvent class
while the "decode_pldm_pdr_repository_change_record_data" will extract
the changeEntries data from the changeRecord. Unit tests for the above
decode functions have been added.

Extracted PDR handles are used to fetch PDRs corresponding to the same
from the host.

Signed-off-by: Zahed Hossain <zahzahed@in.ibm.com>
Signed-off-by: Deepak Kodihalli <dkodihal@in.ibm.com>
Change-Id: I32e8745e04ac82393e067c0f0ab48802809495a6
diff --git a/host_pdr_handler.cpp b/host_pdr_handler.cpp
index 1e78846..3cd7873 100644
--- a/host_pdr_handler.cpp
+++ b/host_pdr_handler.cpp
@@ -5,13 +5,28 @@
 namespace pldm
 {
 
-void HostPDRHandler::fetchPDR(const std::vector<uint32_t>& recordHandles)
+void HostPDRHandler::fetchPDR(std::vector<uint32_t>&& recordHandles)
 {
+    pdrRecordHandles.clear();
+    pdrRecordHandles = std::move(recordHandles);
+
+    // Defer the actual fetch of PDRs from the host (by queuing the call on the
+    // main event loop). That way, we can respond to the platform event msg from
+    // the host firmware.
+    pdrFetchEvent = std::make_unique<sdeventplus::source::Defer>(
+        event, std::bind(std::mem_fn(&HostPDRHandler::_fetchPDR), this,
+                         std::placeholders::_1));
+}
+
+void HostPDRHandler::_fetchPDR(sdeventplus::source::EventBase& /*source*/)
+{
+    pdrFetchEvent.reset();
+
     std::vector<uint8_t> requestMsg(sizeof(pldm_msg_hdr) +
                                     PLDM_GET_PDR_REQ_BYTES);
     auto request = reinterpret_cast<pldm_msg*>(requestMsg.data());
 
-    for (auto recordHandle : recordHandles)
+    for (auto recordHandle : pdrRecordHandles)
     {
         auto instanceId = requester.getInstanceId(mctp_eid);
 
@@ -21,7 +36,8 @@
         if (rc != PLDM_SUCCESS)
         {
             requester.markFree(mctp_eid, instanceId);
-            std::cerr << "Failed to encode_get_pdr_req, rc = " << rc << "\n";
+            std::cerr << "Failed to encode_get_pdr_req, rc = " << rc
+                      << std::endl;
             return;
         }
 
@@ -36,7 +52,7 @@
         if (requesterRc != PLDM_REQUESTER_SUCCESS)
         {
             std::cerr << "Failed to send msg to fetch pdrs, rc = "
-                      << requesterRc << "\n";
+                      << requesterRc << std::endl;
             return;
         }
 
@@ -54,7 +70,8 @@
             &transferFlag, &respCount, nullptr, 0, &transferCRC);
         if (rc != PLDM_SUCCESS)
         {
-            std::cerr << "Failed to decode_get_pdr_resp, rc = " << rc << "\n";
+            std::cerr << "Failed to decode_get_pdr_resp, rc = " << rc
+                      << std::endl;
         }
         else
         {
@@ -67,8 +84,8 @@
             {
                 std::cerr << "Failed to decode_get_pdr_resp: "
                           << "rc=" << rc
-                          << ", cc=" << static_cast<int>(completionCode)
-                          << "\n";
+                          << ", cc=" << static_cast<unsigned>(completionCode)
+                          << std::endl;
             }
             else
             {
diff --git a/host_pdr_handler.hpp b/host_pdr_handler.hpp
index b590e7c..dc5462f 100644
--- a/host_pdr_handler.hpp
+++ b/host_pdr_handler.hpp
@@ -3,7 +3,9 @@
 #include "dbus_impl_requester.hpp"
 #include "utils.hpp"
 
+#include <memory>
 #include <sdeventplus/event.hpp>
+#include <sdeventplus/source/event.hpp>
 #include <vector>
 
 #include "libpldm/base.h"
@@ -14,6 +16,11 @@
 namespace pldm
 {
 
+// vector which would hold the PDR record handle data returned by
+// pldmPDRRepositoryChgEvent event data
+using ChangeEntry = uint32_t;
+using PDRRecordHandles = std::vector<ChangeEntry>;
+
 /** @class HostPDRHandler
  *  @brief This class can fetch and process PDRs from host firmware
  *  @details Provides an API to fetch PDRs from the host firmware. Upon
@@ -52,9 +59,15 @@
      *  @param[in] recordHandles - list of record handles pointing to host's
      *             PDRs that need to be fetched.
      */
-    void fetchPDR(const std::vector<uint32_t>& recordHandles);
+    void fetchPDR(std::vector<uint32_t>&& recordHandles);
 
   private:
+    /** @brief fetchPDR schedules work on the event loop, this method does the
+     *  actual work. This is so that the PDR exchg with the host is async.
+     *  @param[in] source - sdeventplus event source
+     */
+    void _fetchPDR(sdeventplus::source::EventBase& source);
+
     /** @brief fd of MCTP communications socket */
     int mctp_fd;
     /** @brief MCTP EID of host firmware */
@@ -69,6 +82,10 @@
      *  obtain PLDM instance id.
      */
     Requester& requester;
+    /** @brief sdeventplus event source */
+    std::unique_ptr<sdeventplus::source::Defer> pdrFetchEvent;
+    /** @brief list of PDR record handles pointing to host's PDRs */
+    PDRRecordHandles pdrRecordHandles;
 };
 
 } // namespace pldm
diff --git a/libpldmresponder/meson.build b/libpldmresponder/meson.build
index bb0d10c..0275e94 100644
--- a/libpldmresponder/meson.build
+++ b/libpldmresponder/meson.build
@@ -1,6 +1,7 @@
 deps = [
   dependency('phosphor-dbus-interfaces'),
   dependency('sdbusplus'),
+  dependency('sdeventplus'),
   libpldm,
   libpldmutils
 ]
@@ -18,7 +19,8 @@
   'pdr.cpp',
   'platform.cpp',
   'fru_parser.cpp',
-  'fru.cpp'
+  'fru.cpp',
+  '../host_pdr_handler.cpp'
 ]
 
 if get_option('oem-ibm').enabled()
diff --git a/libpldmresponder/platform.cpp b/libpldmresponder/platform.cpp
index 86c5a2c..eb08a8c 100644
--- a/libpldmresponder/platform.cpp
+++ b/libpldmresponder/platform.cpp
@@ -360,6 +360,97 @@
     return PLDM_SUCCESS;
 }
 
+int Handler::pldmPDRRepositoryChgEvent(const pldm_msg* request,
+                                       size_t payloadLength,
+                                       uint8_t /*formatVersion*/,
+                                       uint8_t /*tid*/, size_t eventDataOffset)
+{
+    uint8_t eventDataFormat{};
+    uint8_t numberOfChangeRecords{};
+    size_t dataOffset{};
+
+    auto eventData =
+        reinterpret_cast<const uint8_t*>(request->payload) + eventDataOffset;
+    auto eventDataSize = payloadLength - eventDataOffset;
+
+    auto rc = decode_pldm_pdr_repository_chg_event_data(
+        eventData, eventDataSize, &eventDataFormat, &numberOfChangeRecords,
+        &dataOffset);
+    if (rc != PLDM_SUCCESS)
+    {
+        return rc;
+    }
+
+    PDRRecordHandles pdrRecordHandles;
+    if (eventDataFormat == FORMAT_IS_PDR_HANDLES)
+    {
+        uint8_t eventDataOperation{};
+        uint8_t numberOfChangeEntries{};
+
+        auto changeRecordData = eventData + dataOffset;
+        auto changeRecordDataSize = eventDataSize - dataOffset;
+
+        while (changeRecordDataSize)
+        {
+            rc = decode_pldm_pdr_repository_change_record_data(
+                changeRecordData, changeRecordDataSize, &eventDataOperation,
+                &numberOfChangeEntries, &dataOffset);
+
+            if (rc != PLDM_SUCCESS)
+            {
+                return rc;
+            }
+
+            if (eventDataOperation == PLDM_RECORDS_ADDED)
+            {
+                rc = getPDRRecordHandles(
+                    reinterpret_cast<const ChangeEntry*>(changeRecordData +
+                                                         dataOffset),
+                    changeRecordDataSize - dataOffset,
+                    static_cast<size_t>(numberOfChangeEntries),
+                    pdrRecordHandles);
+
+                if (rc != PLDM_SUCCESS)
+                {
+                    return rc;
+                }
+            }
+
+            changeRecordData +=
+                dataOffset + (numberOfChangeEntries * sizeof(ChangeEntry));
+            changeRecordDataSize -=
+                dataOffset + (numberOfChangeEntries * sizeof(ChangeEntry));
+        }
+
+        if (hostPDRHandler && !pdrRecordHandles.empty())
+        {
+            hostPDRHandler->fetchPDR(std::move(pdrRecordHandles));
+        }
+    }
+    else
+    {
+        return PLDM_ERROR_INVALID_DATA;
+    }
+
+    return PLDM_SUCCESS;
+}
+
+int Handler::getPDRRecordHandles(const ChangeEntry* changeEntryData,
+                                 size_t changeEntryDataSize,
+                                 size_t numberOfChangeEntries,
+                                 PDRRecordHandles& pdrRecordHandles)
+{
+    if (numberOfChangeEntries > (changeEntryDataSize / sizeof(ChangeEntry)))
+    {
+        return PLDM_ERROR_INVALID_DATA;
+    }
+    for (size_t i = 0; i < numberOfChangeEntries; i++)
+    {
+        pdrRecordHandles.push_back(changeEntryData[i]);
+    }
+    return PLDM_SUCCESS;
+}
+
 } // namespace platform
 } // namespace responder
 } // namespace pldm
diff --git a/libpldmresponder/platform.hpp b/libpldmresponder/platform.hpp
index 893a709..94c7c14 100644
--- a/libpldmresponder/platform.hpp
+++ b/libpldmresponder/platform.hpp
@@ -83,6 +83,13 @@
                 return this->sensorEvent(request, payloadLength, formatVersion,
                                          tid, eventDataOffset);
             });
+        eventHandlers[PLDM_PDR_REPOSITORY_CHG_EVENT].emplace_back(
+            [this](const pldm_msg* request, size_t payloadLength,
+                   uint8_t formatVersion, uint8_t tid, size_t eventDataOffset) {
+                return this->pldmPDRRepositoryChgEvent(request, payloadLength,
+                                                       formatVersion, tid,
+                                                       eventDataOffset);
+            });
 
         // Additional OEM event handlers for PLDM events, append it to the
         // standard handlers
@@ -198,6 +205,35 @@
     int sensorEvent(const pldm_msg* request, size_t payloadLength,
                     uint8_t formatVersion, uint8_t tid, size_t eventDataOffset);
 
+    /** @brief Handler for pldmPDRRepositoryChgEvent
+     *
+     *  @param[in] request - Request message
+     *  @param[in] payloadLength - Request payload length
+     *  @param[in] formatVersion - Version of the event format
+     *  @param[in] tid - Terminus ID of the event's originator
+     *  @param[in] eventDataOffset - Offset of the event data in the request
+     *                               message
+     *  @return PLDM completion code
+     */
+    int pldmPDRRepositoryChgEvent(const pldm_msg* request, size_t payloadLength,
+                                  uint8_t formatVersion, uint8_t tid,
+                                  size_t eventDataOffset);
+
+    /** @brief Handler for extracting the PDR handles from changeEntries
+     *
+     *  @param[in] changeEntryData - ChangeEntry data from changeRecord
+     *  @param[in] changeEntryDataSize - total size of changeEntryData
+     *  @param[in] numberOfChangeEntries - total number of changeEntries to
+     *                                     extract
+     *  @param[out] pdrRecordHandles - std::vector where the extracted PDR
+     *                                 handles are placed
+     *  @return PLDM completion code
+     */
+    int getPDRRecordHandles(const ChangeEntry* changeEntryData,
+                            size_t changeEntryDataSize,
+                            size_t numberOfChangeEntries,
+                            PDRRecordHandles& pdrRecordHandles);
+
     /** @brief Handler for setting Sensor event data
      *
      *  @param[in] sensorId - sensorID value of the sensor
diff --git a/meson.build b/meson.build
index d6fd8c9..bb03bc4 100644
--- a/meson.build
+++ b/meson.build
@@ -67,7 +67,6 @@
   'pldmd.cpp',
   'dbus_impl_requester.cpp',
   'instance_id.cpp',
-  'host_pdr_handler.cpp',
   implicit_include_directories: false,
   dependencies: deps,
   install: true,
diff --git a/test/meson.build b/test/meson.build
index 3adab01..b9d5b74 100644
--- a/test/meson.build
+++ b/test/meson.build
@@ -56,6 +56,7 @@
                          gmock,
                          pldmd,
                          dependency('phosphor-dbus-interfaces'),
+                         dependency('sdeventplus'),
                          dependency('sdbusplus')]),
        workdir: meson.current_source_dir())
 endforeach