Handle FRU records from host

- Get FRU record table from host and traverse the entity structure
  from the FRU record set PDR, and create D-Bus object.

- When the FRU field type is a PLDM_OEM_FRU_FIELD_TYPE_LOCATION_CODE
  , the location code is used to  populate the
  xyz.openbmc_project.Inventory.Decorator.LocationCode D-Bus
  interface for the corresponding FRU.

Signed-off-by: George Liu <liuxiwei@inspur.com>
Change-Id: I482c4d371f76b4881109ef420dfd9543e1aa810b
diff --git a/host-bmc/custom_dbus.cpp b/host-bmc/custom_dbus.cpp
new file mode 100644
index 0000000..6427a93
--- /dev/null
+++ b/host-bmc/custom_dbus.cpp
@@ -0,0 +1,31 @@
+#include "custom_dbus.hpp"
+
+namespace pldm
+{
+namespace dbus
+{
+void CustomDBus::setLocationCode(const std::string& path, std::string value)
+{
+    if (!location.contains(path))
+    {
+        location.emplace(path,
+                         std::make_unique<LocationIntf>(
+                             pldm::utils::DBusHandler::getBus(), path.c_str()));
+    }
+
+    location.at(path)->locationCode(value);
+}
+
+std::optional<std::string>
+    CustomDBus::getLocationCode(const std::string& path) const
+{
+    if (location.contains(path))
+    {
+        return location.at(path)->locationCode();
+    }
+
+    return std::nullopt;
+}
+
+} // namespace dbus
+} // namespace pldm
diff --git a/host-bmc/custom_dbus.hpp b/host-bmc/custom_dbus.hpp
new file mode 100644
index 0000000..6ff7daa
--- /dev/null
+++ b/host-bmc/custom_dbus.hpp
@@ -0,0 +1,68 @@
+#pragma once
+
+#include "common/utils.hpp"
+
+#include <sdbusplus/server.hpp>
+#include <xyz/openbmc_project/Inventory/Decorator/LocationCode/server.hpp>
+
+#include <memory>
+#include <optional>
+#include <string>
+#include <unordered_map>
+
+namespace pldm
+{
+namespace dbus
+{
+using ObjectPath = std::string;
+
+using LocationIntf = sdbusplus::server::object::object<
+    sdbusplus::xyz::openbmc_project::Inventory::Decorator::server::
+        LocationCode>;
+
+/** @class CustomDBus
+ *  @brief This is a custom D-Bus object, used to add D-Bus interface and update
+ *         the corresponding properties value.
+ */
+class CustomDBus
+{
+  private:
+    CustomDBus() {}
+
+  public:
+    CustomDBus(const CustomDBus&) = delete;
+    CustomDBus(CustomDBus&&) = delete;
+    CustomDBus& operator=(const CustomDBus&) = delete;
+    CustomDBus& operator=(CustomDBus&&) = delete;
+    ~CustomDBus() = default;
+
+    static CustomDBus& getCustomDBus()
+    {
+        static CustomDBus customDBus;
+        return customDBus;
+    }
+
+  public:
+    /** @brief Set the LocationCode property
+     *
+     *  @param[in] path  - The object path
+     *
+     *  @param[in] value - The value of the LocationCode property
+     */
+    void setLocationCode(const std::string& path, std::string value);
+
+    /** @brief Get the LocationCode property
+     *
+     *  @param[in] path     - The object path
+     *
+     *  @return std::optional<std::string> - The value of the LocationCode
+     *          property
+     */
+    std::optional<std::string> getLocationCode(const std::string& path) const;
+
+  private:
+    std::unordered_map<ObjectPath, std::unique_ptr<LocationIntf>> location;
+};
+
+} // namespace dbus
+} // namespace pldm
diff --git a/host-bmc/host_pdr_handler.cpp b/host-bmc/host_pdr_handler.cpp
index 3ceae13..32993ee 100644
--- a/host-bmc/host_pdr_handler.cpp
+++ b/host-bmc/host_pdr_handler.cpp
@@ -1,5 +1,12 @@
 #include "host_pdr_handler.hpp"
 
+#include "libpldm/fru.h"
+#ifdef OEM_IBM
+#include "libpldm/fru_oem_ibm.h"
+#endif
+
+#include "custom_dbus.hpp"
+
 #include <assert.h>
 #include <libpldm/pldm.h>
 
@@ -21,6 +28,7 @@
 using namespace sdbusplus::bus::match::rules;
 using Json = nlohmann::json;
 namespace fs = std::filesystem;
+using namespace pldm::dbus;
 constexpr auto fruJson = "host_frus.json";
 const Json emptyJson{};
 const std::vector<Json> emptyJsonList{};
@@ -403,6 +411,7 @@
 {
     static bool merged = false;
     static PDRList stateSensorPDRs{};
+    static PDRList fruRecordSetPDRs{};
     uint32_t nextRecordHandle{};
     uint8_t tlEid = 0;
     bool tlValid = true;
@@ -521,6 +530,7 @@
                 {
                     pdrTerminusHandle =
                         extractTerminusHandle<pldm_pdr_fru_record_set>(pdr);
+                    fruRecordSetPDRs.emplace_back(pdr);
                 }
                 else if (pdrHdr->type == PLDM_STATE_EFFECTER_PDR)
                 {
@@ -558,11 +568,13 @@
 
         /*received last record*/
         this->parseStateSensorPDRs(stateSensorPDRs);
+        this->createDbusObjects(fruRecordSetPDRs);
         if (isHostUp())
         {
             this->setHostSensorState(stateSensorPDRs);
         }
         stateSensorPDRs.clear();
+        fruRecordSetPDRs.clear();
         entityAssociations.clear();
 
         if (merged)
@@ -817,4 +829,211 @@
         }
     }
 }
+
+void HostPDRHandler::getFRURecordTableMetadataByRemote(
+    const PDRList& fruRecordSetPDRs)
+{
+    auto instanceId = instanceIdDb.next(mctp_eid);
+    std::vector<uint8_t> requestMsg(
+        sizeof(pldm_msg_hdr) + PLDM_GET_FRU_RECORD_TABLE_METADATA_REQ_BYTES);
+
+    // GetFruRecordTableMetadata
+    auto request = reinterpret_cast<pldm_msg*>(requestMsg.data());
+    auto rc = encode_get_fru_record_table_metadata_req(
+        instanceId, request, requestMsg.size() - sizeof(pldm_msg_hdr));
+    if (rc != PLDM_SUCCESS)
+    {
+        instanceIdDb.free(mctp_eid, instanceId);
+        lg2::error(
+            "Failed to encode_get_fru_record_table_metadata_req, rc = {RC}",
+            "RC", lg2::hex, rc);
+        return;
+    }
+
+    auto getFruRecordTableMetadataResponseHandler =
+        [this, fruRecordSetPDRs](mctp_eid_t /*eid*/, const pldm_msg* response,
+                                 size_t respMsgLen) {
+        if (response == nullptr || !respMsgLen)
+        {
+            lg2::error(
+                "Failed to receive response for the Get FRU Record Table Metadata");
+            return;
+        }
+
+        uint8_t cc = 0;
+        uint8_t fru_data_major_version, fru_data_minor_version;
+        uint32_t fru_table_maximum_size, fru_table_length;
+        uint16_t total_record_set_identifiers;
+        uint16_t total;
+        uint32_t checksum;
+
+        auto rc = decode_get_fru_record_table_metadata_resp(
+            response, respMsgLen, &cc, &fru_data_major_version,
+            &fru_data_minor_version, &fru_table_maximum_size, &fru_table_length,
+            &total_record_set_identifiers, &total, &checksum);
+
+        if (rc != PLDM_SUCCESS || cc != PLDM_SUCCESS)
+        {
+            lg2::error(
+                "Faile to decode get fru record table metadata resp, Message Error: {RC}, cc: {CC}",
+                "RC", lg2::hex, rc, "CC", cc);
+            return;
+        }
+
+        // pass total to getFRURecordTableByRemote
+        this->getFRURecordTableByRemote(fruRecordSetPDRs, total);
+    };
+
+    rc = handler->registerRequest(
+        mctp_eid, instanceId, PLDM_FRU, PLDM_GET_FRU_RECORD_TABLE_METADATA,
+        std::move(requestMsg),
+        std::move(getFruRecordTableMetadataResponseHandler));
+    if (rc != PLDM_SUCCESS)
+    {
+        lg2::error("Failed to send the the Set State Effecter States request");
+    }
+
+    return;
+}
+
+void HostPDRHandler::getFRURecordTableByRemote(const PDRList& fruRecordSetPDRs,
+                                               uint16_t totalTableRecords)
+{
+    fruRecordData.clear();
+
+    if (!totalTableRecords)
+    {
+        lg2::error("Failed to get fru record table");
+        return;
+    }
+
+    auto instanceId = instanceIdDb.next(mctp_eid);
+    std::vector<uint8_t> requestMsg(sizeof(pldm_msg_hdr) +
+                                    PLDM_GET_FRU_RECORD_TABLE_REQ_BYTES);
+
+    // send the getFruRecordTable command
+    auto request = reinterpret_cast<pldm_msg*>(requestMsg.data());
+    auto rc = encode_get_fru_record_table_req(
+        instanceId, 0, PLDM_GET_FIRSTPART, request,
+        requestMsg.size() - sizeof(pldm_msg_hdr));
+    if (rc != PLDM_SUCCESS)
+    {
+        instanceIdDb.free(mctp_eid, instanceId);
+        lg2::error("Failed to encode_get_fru_record_table_req, rc = {RC}", "RC",
+                   lg2::hex, rc);
+        return;
+    }
+
+    auto getFruRecordTableResponseHandler =
+        [totalTableRecords, this, fruRecordSetPDRs](
+            mctp_eid_t /*eid*/, const pldm_msg* response, size_t respMsgLen) {
+        if (response == nullptr || !respMsgLen)
+        {
+            lg2::error(
+                "Failed to receive response for the Get FRU Record Table");
+            return;
+        }
+
+        uint8_t cc = 0;
+        uint32_t next_data_transfer_handle = 0;
+        uint8_t transfer_flag = 0;
+        size_t fru_record_table_length = 0;
+        std::vector<uint8_t> fru_record_table_data(respMsgLen -
+                                                   sizeof(pldm_msg_hdr));
+        auto responsePtr = reinterpret_cast<const struct pldm_msg*>(response);
+        auto rc = decode_get_fru_record_table_resp(
+            responsePtr, respMsgLen - sizeof(pldm_msg_hdr), &cc,
+            &next_data_transfer_handle, &transfer_flag,
+            fru_record_table_data.data(), &fru_record_table_length);
+
+        if (rc != PLDM_SUCCESS || cc != PLDM_SUCCESS)
+        {
+            lg2::error(
+                "Failed to decode get fru record table resp, Message Error: {RC}, cc: {CC}",
+                "RC", lg2::hex, rc, "CC", cc);
+            return;
+        }
+
+        fruRecordData = responder::pdr_utils::parseFruRecordTable(
+            fru_record_table_data.data(), fru_record_table_length);
+
+        if (totalTableRecords != fruRecordData.size())
+        {
+            fruRecordData.clear();
+
+            lg2::error("failed to parse fru recrod data format.");
+            return;
+        }
+
+        this->setFRUDataOnDBus(fruRecordSetPDRs, fruRecordData);
+    };
+
+    rc = handler->registerRequest(
+        mctp_eid, instanceId, PLDM_FRU, PLDM_GET_FRU_RECORD_TABLE,
+        std::move(requestMsg), std::move(getFruRecordTableResponseHandler));
+    if (rc != PLDM_SUCCESS)
+    {
+        lg2::error("Failed to send the the Set State Effecter States request");
+    }
+}
+
+std::optional<uint16_t> HostPDRHandler::getRSI(const PDRList& fruRecordSetPDRs,
+                                               const pldm_entity& entity)
+{
+    for (const auto& pdr : fruRecordSetPDRs)
+    {
+        auto fruPdr = reinterpret_cast<const pldm_pdr_fru_record_set*>(
+            const_cast<uint8_t*>(pdr.data()) + sizeof(pldm_pdr_hdr));
+
+        if (fruPdr->entity_type == entity.entity_type &&
+            fruPdr->entity_instance_num == entity.entity_instance_num)
+        {
+            return fruPdr->fru_rsi;
+        }
+    }
+
+    return std::nullopt;
+}
+
+void HostPDRHandler::setFRUDataOnDBus(
+    const PDRList& fruRecordSetPDRs,
+    const std::vector<responder::pdr_utils::FruRecordDataFormat>& fruRecordData)
+{
+    for (const auto& entity : objPathMap)
+    {
+        pldm_entity node = pldm_entity_extract(entity.second);
+        auto fruRSI = getRSI(fruRecordSetPDRs, node);
+
+        for (const auto& data : fruRecordData)
+        {
+            if (!fruRSI || *fruRSI != data.fruRSI)
+            {
+                continue;
+            }
+
+            if (data.fruRecType == PLDM_FRU_RECORD_TYPE_OEM)
+            {
+                for (const auto& tlv : data.fruTLV)
+                {
+                    if (tlv.fruFieldType ==
+                        PLDM_OEM_FRU_FIELD_TYPE_LOCATION_CODE)
+                    {
+                        CustomDBus::getCustomDBus().setLocationCode(
+                            entity.first,
+                            std::string(reinterpret_cast<const char*>(
+                                            tlv.fruFieldValue.data()),
+                                        tlv.fruFieldLen));
+                    }
+                }
+            }
+        }
+    }
+}
+void HostPDRHandler::createDbusObjects(const PDRList& fruRecordSetPDRs)
+{
+    // TODO: Creating and Refreshing dbus hosted by remote PLDM entity Fru PDRs
+
+    getFRURecordTableMetadataByRemote(fruRecordSetPDRs);
+}
+
 } // namespace pldm
diff --git a/host-bmc/host_pdr_handler.hpp b/host-bmc/host_pdr_handler.hpp
index 5dc5e9e..1821005 100644
--- a/host-bmc/host_pdr_handler.hpp
+++ b/host-bmc/host_pdr_handler.hpp
@@ -205,6 +205,48 @@
     void _processFetchPDREvent(uint32_t nextRecordHandle,
                                sdeventplus::source::EventBase& source);
 
+    /** @brief Get FRU record table metadata by remote PLDM terminus
+     *
+     *  @param[out] uint16_t    - total table records
+     */
+    void getFRURecordTableMetadataByRemote(const PDRList& fruRecordSetPDRs);
+
+    /** @brief Set Location Code in the dbus objects
+     *
+     *  @param[in] fruRecordSetPDRs - the Fru Record set PDR's
+     *  @param[in] fruRecordData - the Fru Record Data
+     */
+
+    void setFRUDataOnDBus(
+        const PDRList& fruRecordSetPDRs,
+        const std::vector<responder::pdr_utils::FruRecordDataFormat>&
+            fruRecordData);
+
+    /** @brief Get FRU record table by remote PLDM terminus
+     *
+     *  @param[in] fruRecordSetPDRs  - the Fru Record set PDR's
+     *  @param[in] totalTableRecords - the Number of total table records
+     *  @return
+     */
+    void getFRURecordTableByRemote(const PDRList& fruRecordSetPDRs,
+                                   uint16_t totalTableRecords);
+
+    /** @brief Create Dbus objects by remote PLDM entity Fru PDRs
+     *
+     *  @param[in] fruRecordSetPDRs - fru record set pdr
+     *
+     * @ return
+     */
+    void createDbusObjects(const PDRList& fruRecordSetPDRs);
+
+    /** @brief Get FRU Record Set Identifier from FRU Record data Format
+     *  @param[in] fruRecordSetPDRs - fru record set pdr
+     *  @param[in] entity           - PLDM entity information
+     *  @return
+     */
+    std::optional<uint16_t> getRSI(const PDRList& fruRecordSetPDRs,
+                                   const pldm_entity& entity);
+
     /** @brief fd of MCTP communications socket */
     int mctp_fd;
     /** @brief MCTP EID of host firmware */
@@ -277,6 +319,14 @@
     /** @brief maps an entity name to map, maps to entity name to pldm_entity
      */
     utils::EntityAssociations entityAssociations;
+
+    /** @brief the vector of FRU Record Data Format
+     */
+    std::vector<responder::pdr_utils::FruRecordDataFormat> fruRecordData;
+
+    /** @brief Object path and entity association and is only loaded once
+     */
+    bool objPathEntityAssociation;
 };
 
 } // namespace pldm
diff --git a/host-bmc/test/custom_dbus_test.cpp b/host-bmc/test/custom_dbus_test.cpp
new file mode 100644
index 0000000..4549c0d
--- /dev/null
+++ b/host-bmc/test/custom_dbus_test.cpp
@@ -0,0 +1,16 @@
+#include "../custom_dbus.hpp"
+
+#include <gtest/gtest.h>
+
+using namespace pldm::dbus;
+TEST(CustomDBus, LocationCode)
+{
+    std::string tmpPath = "/abc/def";
+    std::string locationCode = "testLocationCode";
+
+    CustomDBus::getCustomDBus().setLocationCode(tmpPath, locationCode);
+    auto retLocationCode = CustomDBus::getCustomDBus().getLocationCode(tmpPath);
+
+    EXPECT_NE(retLocationCode, std::nullopt);
+    EXPECT_EQ(locationCode, retLocationCode);
+}
diff --git a/host-bmc/test/meson.build b/host-bmc/test/meson.build
index d19d4ef..9dee59f 100644
--- a/host-bmc/test/meson.build
+++ b/host-bmc/test/meson.build
@@ -4,11 +4,13 @@
 
 test_sources = [
   '../../common/utils.cpp',
+  '../custom_dbus.cpp',
 ]
 
 tests = [
   'dbus_to_host_effecter_test',
   'utils_test',
+  'custom_dbus_test',
 ]
 
 foreach t : tests
diff --git a/libpldmresponder/meson.build b/libpldmresponder/meson.build
index ad4207b..7de2cf2 100644
--- a/libpldmresponder/meson.build
+++ b/libpldmresponder/meson.build
@@ -26,6 +26,7 @@
   '../host-bmc/dbus_to_event_handler.cpp',
   '../host-bmc/dbus_to_host_effecters.cpp',
   '../host-bmc/host_condition.cpp',
+  '../host-bmc/custom_dbus.cpp',
   'event_parser.cpp'
 ]
 
diff --git a/libpldmresponder/pdr_utils.cpp b/libpldmresponder/pdr_utils.cpp
index 7e5e491..06832a1 100644
--- a/libpldmresponder/pdr_utils.cpp
+++ b/libpldmresponder/pdr_utils.cpp
@@ -1,3 +1,5 @@
+#include "libpldm/fru.h"
+
 #include "pdr.hpp"
 
 #include <libpldm/platform.h>
@@ -16,6 +18,15 @@
 {
 namespace pdr_utils
 {
+// Refer: DSP0257_1.0.0 Table 2
+// 7: uint16_t(FRU Record Set Identifier), uint8_t(FRU Record Type),
+// uint8_t(Number of FRU fields), uint8_t(Encoding Type for FRU fields),
+// uint8_t(FRU Field Type), uint8_t(FRU Field Length)
+static constexpr uint8_t fruRecordDataFormatLength = 7;
+
+// // 2: 1byte FRU Field Type, 1byte FRU Field Length
+static constexpr uint8_t fruFieldTypeLength = 2;
+
 pldm_pdr* Repo::getPdr() const
 {
     return repo;
@@ -196,6 +207,58 @@
                            std::move(sensorInfo));
 }
 
+std::vector<FruRecordDataFormat> parseFruRecordTable(const uint8_t* fruData,
+                                                     size_t fruLen)
+{
+    // Refer: DSP0257_1.0.0 Table 2
+    // 7: uint16_t(FRU Record Set Identifier), uint8_t(FRU Record Type),
+    // uint8_t(Number of FRU fields), uint8_t(Encoding Type for FRU fields),
+    // uint8_t(FRU Field Type), uint8_t(FRU Field Length)
+    if (fruLen < fruRecordDataFormatLength)
+    {
+        lg2::error("Invalid fru len: {FRULEN}", "FRULEN", fruLen);
+        return {};
+    }
+
+    std::vector<FruRecordDataFormat> frus;
+
+    size_t index = 0;
+    while (index < fruLen)
+    {
+        FruRecordDataFormat fru;
+
+        auto record = reinterpret_cast<const pldm_fru_record_data_format*>(
+            fruData + index);
+        fru.fruRSI = (int)le16toh(record->record_set_id);
+        fru.fruRecType = record->record_type;
+        fru.fruNum = record->num_fru_fields;
+        fru.fruEncodeType = record->encoding_type;
+
+        index += 5;
+
+        std::ranges::for_each(std::views::iota(0, (int)record->num_fru_fields),
+                              [fruData, &fru, &index](int) {
+            auto tlv =
+                reinterpret_cast<const pldm_fru_record_tlv*>(fruData + index);
+            FruTLV frutlv;
+            frutlv.fruFieldType = tlv->type;
+            frutlv.fruFieldLen = tlv->length;
+            frutlv.fruFieldValue.resize(tlv->length);
+            for (const auto& i : std::views::iota(0, (int)tlv->length))
+            {
+                memcpy(frutlv.fruFieldValue.data() + i, tlv->value + i, 1);
+            }
+            fru.fruTLV.push_back(frutlv);
+
+            // 2: 1byte FRU Field Type, 1byte FRU Field Length
+            index += fruFieldTypeLength + (unsigned)tlv->length;
+        });
+
+        frus.push_back(fru);
+    }
+
+    return frus;
+}
 } // namespace pdr_utils
 } // namespace responder
 } // namespace pldm
diff --git a/libpldmresponder/pdr_utils.hpp b/libpldmresponder/pdr_utils.hpp
index 739058e..b44120a 100644
--- a/libpldmresponder/pdr_utils.hpp
+++ b/libpldmresponder/pdr_utils.hpp
@@ -38,6 +38,22 @@
     PLDM_SENSOR_ID
 };
 
+struct FruTLV
+{
+    uint8_t fruFieldType;
+    uint8_t fruFieldLen;
+    std::vector<uint8_t> fruFieldValue;
+};
+
+struct FruRecordDataFormat
+{
+    uint16_t fruRSI;
+    uint8_t fruRecType;
+    uint8_t fruNum;
+    uint8_t fruEncodeType;
+    std::vector<FruTLV> fruTLV;
+};
+
 /** @struct PdrEntry
  *  PDR entry structure that acts as a PDR record structure in the PDR
  *  repository to handle PDR APIs.
@@ -214,6 +230,18 @@
            pldm::pdr::SensorInfo>
     parseStateSensorPDR(const std::vector<uint8_t>& stateSensorPdr);
 
+/** @brief Parse FRU record table and return the vector of the FRU record data
+ *         format structure
+ *
+ *  @param[in] fruData - fru data
+ *  @param[in] fruLen  - fru len
+ *
+ *  @return std::vector<FruRecordDataFormat> - the vector of the FRU record data
+ *          format structure
+ */
+std::vector<FruRecordDataFormat> parseFruRecordTable(const uint8_t* fruData,
+                                                     size_t fruLen);
+
 } // namespace pdr_utils
 } // namespace responder
 } // namespace pldm
diff --git a/pldmd/pldmd.cpp b/pldmd/pldmd.cpp
index b8315b7..8e57f20 100644
--- a/pldmd/pldmd.cpp
+++ b/pldmd/pldmd.cpp
@@ -202,6 +202,8 @@
     InstanceIdDb instanceIdDb;
     dbus_api::Requester dbusImplReq(bus, "/xyz/openbmc_project/pldm",
                                     instanceIdDb);
+    sdbusplus::server::manager::manager inventoryManager(
+        bus, "/xyz/openbmc_project/inventory");
 
     Invoker invoker{};
     requester::Handler<requester::Request> reqHandler(