fw_update: Introduce FirmwareInventory

Spec reference: [1]

Description:
FirmwareInventory is a class that manages all of the D-Bus
utilities for the firmware update functionality of each entry,
FirmwareInventoryManager is used to manage multiple FirmwareInventory
instances.

Flow of the routine when one or more firmware device is added:
1. When one or more firmware device is added, the InventoryManager
   instance will try to create a FirmwareInventory entry for the added
   device, to accomplish this, it will need to fetch the necessary
   information from various source (e.g. EM, device descriptors).

2. After the information is gathered, the InventoryManager will try to
   obtain a Name for each firmware devices, to fit the different
   platform condition, the name will be reference by the order below:
    1. From Entity Manager (EM), if the endpoint has a specific EM
       configuration with "Name" property listed.
    2. From device descriptors, if the device descriptor contains a
       vendor defined descriptor with "OpenBMC.Name" titled.
    3. Spawn a default one, named "Firmware_Device_<Endpoint ID>".

3. The InventoryManager will then invoke
   `FirmwareInventoryManager::createFirmwareInventory` to create a
   firmware inventory entry.

Properties managed by firmware Inventory:
1. Version
   Version object that represents the firmware version
2. Association
   Association object that represents the associations for the
   firmware

The object path pattern of the firmware inventory entry is:
`/xyz/openbmc_project/software/<BoardName>_<SoftwareName>_<SoftwareID>`

Where:
- `<BoardName>` represents the board the device is on.
- `<SoftwareName>` is the name of the firmware device obtained from the
  InventoryManager.
- `<SoftwareID>` is a 4-byte random number to help consumer
  distinguish from the new/old objects of a inventory item.
  For example:
  `server_board_slot_1_VR_2603`
  The new Activation object and its related interfaces needs to
  resides on a new objectPath, hence the softwareId is appended to
  the path.

Test results:
 - Build passed
 - Successfully create firmware inventory entries
   on Yosemite V4

[1]: https://github.com/openbmc/docs/blob/master/designs/code-update.md

Change-Id: Idebd7d013c82c60f08309a1860d5de1deeb3829a
Signed-off-by: Unive Tien <unive.tien.wiwynn@gmail.com>
Signed-off-by: Carter Chen <carter.chen.wiwynn@gmail.com>
diff --git a/common/test/mocked_utils.hpp b/common/test/mocked_utils.hpp
index 1cec506..4cce83e 100644
--- a/common/test/mocked_utils.hpp
+++ b/common/test/mocked_utils.hpp
@@ -81,4 +81,8 @@
 
     MOCK_METHOD(pldm::utils::PropertyMap, getDbusPropertiesVariant,
                 (const char*, const char*, const char*), (const override));
+
+    MOCK_METHOD(pldm::utils::GetAncestorsResponse, getAncestors,
+                (const std::string&, const std::vector<std::string>&),
+                (const, override));
 };
diff --git a/common/types.hpp b/common/types.hpp
index 18542d9..7bbe22f 100644
--- a/common/types.hpp
+++ b/common/types.hpp
@@ -5,6 +5,7 @@
 #include <bitset>
 #include <cstdint>
 #include <map>
+#include <memory>
 #include <set>
 #include <string>
 #include <unordered_map>
@@ -100,6 +101,8 @@
 
 namespace fw_update
 {
+using InventoryPath = std::string;
+using SoftwareName = std::string;
 
 // Descriptor definition
 using DescriptorType = uint16_t;
@@ -121,6 +124,7 @@
 // Component information
 using CompClassification = uint16_t;
 using CompIdentifier = uint16_t;
+using SoftwareIdentifier = std::pair<eid, CompIdentifier>;
 using CompKey = std::pair<CompClassification, CompIdentifier>;
 using CompClassificationIndex = uint8_t;
 using ComponentInfo = std::map<CompKey, CompClassificationIndex>;
diff --git a/common/utils.cpp b/common/utils.cpp
index 7406e1f..e2cbd50 100644
--- a/common/utils.cpp
+++ b/common/utils.cpp
@@ -953,5 +953,11 @@
         }
     }
 }
+
+long int generateSwId()
+{
+    return random() % 10000;
+}
+
 } // namespace utils
 } // namespace pldm
diff --git a/common/utils.hpp b/common/utils.hpp
index c031af1..867e064 100644
--- a/common/utils.hpp
+++ b/common/utils.hpp
@@ -699,6 +699,12 @@
 std::optional<uint32_t> fruFieldParserU32(const uint8_t* value,
                                           const uint8_t& length);
 
+/**
+ * @brief Get a random ID for firmware inventory entry
+ * @return Random ID as long integer
+ */
+long int generateSwId();
+
 /** @brief Method to get the value from a bios attribute
  *
  *  @param[in] dbusAttrName - the bios attribute name from
diff --git a/fw-update/firmware_inventory.cpp b/fw-update/firmware_inventory.cpp
new file mode 100644
index 0000000..e59b5ba
--- /dev/null
+++ b/fw-update/firmware_inventory.cpp
@@ -0,0 +1,23 @@
+#include "firmware_inventory.hpp"
+
+namespace pldm::fw_update
+{
+
+FirmwareInventory::FirmwareInventory(
+    SoftwareIdentifier /*softwareIdentifier*/, const std::string& softwarePath,
+    const std::string& softwareVersion, const std::string& associatedEndpoint,
+    const Descriptors& /*descriptors*/, const ComponentInfo& /*componentInfo*/,
+    SoftwareVersionPurpose purpose) :
+    softwarePath(softwarePath),
+    association(this->bus, this->softwarePath.c_str()),
+    version(this->bus, this->softwarePath.c_str(),
+            SoftwareVersion::action::defer_emit)
+{
+    this->association.associations(
+        {{"running", "ran_on", associatedEndpoint.c_str()}});
+    this->version.version(softwareVersion.c_str());
+    this->version.purpose(purpose);
+    this->version.emit_added();
+}
+
+} // namespace pldm::fw_update
diff --git a/fw-update/firmware_inventory.hpp b/fw-update/firmware_inventory.hpp
new file mode 100644
index 0000000..729989e
--- /dev/null
+++ b/fw-update/firmware_inventory.hpp
@@ -0,0 +1,83 @@
+#pragma once
+
+#include "common/types.hpp"
+#include "common/utils.hpp"
+
+#include <xyz/openbmc_project/Association/Definitions/server.hpp>
+#include <xyz/openbmc_project/Software/Version/server.hpp>
+
+class FirmwareInventoryTest;
+
+namespace pldm::fw_update
+{
+
+class FirmwareInventory;
+using SoftwareVersion = sdbusplus::server::object_t<
+    sdbusplus::xyz::openbmc_project::Software::server::Version>;
+using SoftwareAssociationDefinitions = sdbusplus::server::object_t<
+    sdbusplus::xyz::openbmc_project::Association::server::Definitions>;
+
+using SoftwareVersionPurpose = SoftwareVersion::VersionPurpose;
+
+using namespace pldm;
+
+class FirmwareInventory
+{
+  public:
+    friend class ::FirmwareInventoryTest;
+    FirmwareInventory() = delete;
+    FirmwareInventory(const FirmwareInventory&) = delete;
+    FirmwareInventory(FirmwareInventory&&) = delete;
+    FirmwareInventory& operator=(const FirmwareInventory&) = delete;
+    FirmwareInventory& operator=(FirmwareInventory&&) = delete;
+    ~FirmwareInventory() = default;
+
+    /**
+     * @brief Constructor
+     * @param[in] softwareIdentifier - Software identifier containing EID and
+     *                                 component identifier
+     * @param[in] softwarePath - D-Bus object path for the firmware inventory
+     * entry
+     * @param[in] softwareVersion - Active version of the firmware
+     * @param[in] associatedEndpoint - D-Bus object path of the endpoint
+     * associated with the firmware
+     * @param[in] descriptors - Descriptors associated with the firmware
+     * @param[in] componentInfo - Component information associated with the
+     * firmware
+     * @param[in] purpose - Purpose of the software version, default is Unknown
+     * @note The descriptors and componentInfo parameters are reserved for
+     * future use and currently not used in the implementation.
+     */
+    explicit FirmwareInventory(
+        SoftwareIdentifier /*softwareIdentifier*/,
+        const std::string& softwarePath, const std::string& softwareVersion,
+        const std::string& associatedEndpoint,
+        const Descriptors& /*descriptors*/,
+        const ComponentInfo& /*componentInfo*/,
+        SoftwareVersionPurpose purpose = SoftwareVersionPurpose::Unknown);
+
+  private:
+    /**
+     * @brief Reference to the sdbusplus bus
+     */
+    sdbusplus::bus_t& bus = utils::DBusHandler::getBus();
+
+    /**
+     * @brief The D-Bus object path for the firmware inventory entry, obtained
+     * by
+     */
+    std::string softwarePath;
+
+    /**
+     * @brief Software association object that represents the associations
+     *        for the firmware
+     */
+    SoftwareAssociationDefinitions association;
+
+    /**
+     * @brief Software version object that represents the firmware version
+     */
+    SoftwareVersion version;
+};
+
+} // namespace pldm::fw_update
diff --git a/fw-update/firmware_inventory_manager.cpp b/fw-update/firmware_inventory_manager.cpp
new file mode 100644
index 0000000..4b09b4f
--- /dev/null
+++ b/fw-update/firmware_inventory_manager.cpp
@@ -0,0 +1,96 @@
+#include "firmware_inventory_manager.hpp"
+
+#include "common/types.hpp"
+#include "common/utils.hpp"
+
+#include <phosphor-logging/lg2.hpp>
+
+PHOSPHOR_LOG2_USING;
+
+namespace pldm::fw_update
+{
+
+void FirmwareInventoryManager::createFirmwareEntry(
+    const SoftwareIdentifier& softwareIdentifier,
+    const SoftwareName& softwareName, const std::string& activeVersion,
+    const Descriptors& descriptors, const ComponentInfo& componentInfo)
+{
+    struct timespec ts;
+    clock_gettime(CLOCK_REALTIME, &ts);
+    unsigned seed = ts.tv_nsec ^ getpid();
+    srandom(seed);
+
+    auto& eid = softwareIdentifier.first;
+    const auto inventoryPath = getInventoryPath(eid);
+    if (!inventoryPath)
+    {
+        error("No inventory path found for EID {EID}", "EID", eid);
+        return;
+    }
+    auto boardPath = getBoardPath(*dbusHandler, *inventoryPath);
+
+    if (!boardPath)
+    {
+        error("Failed to get board path for EID {EID}", "EID", eid);
+        return;
+    }
+    const auto boardName = boardPath->filename().string();
+    const auto softwarePath =
+        std::format("/xyz/openbmc_project/software/{}_{}_{}", boardName,
+                    softwareName, utils::generateSwId());
+
+    softwareMap.insert_or_assign(
+        softwareIdentifier, std::make_unique<FirmwareInventory>(
+                                softwareIdentifier, softwarePath, activeVersion,
+                                *boardPath, descriptors, componentInfo));
+}
+
+void FirmwareInventoryManager::deleteFirmwareEntry(const pldm::eid& eid)
+{
+    std::erase_if(softwareMap,
+                  [&](const auto& pair) { return pair.first.first == eid; });
+}
+
+std::optional<std::filesystem::path> getBoardPath(
+    const pldm::utils::DBusHandler& handler, const InventoryPath& path)
+{
+    constexpr auto boardInterface = "xyz.openbmc_project.Inventory.Item.Board";
+    pldm::utils::GetAncestorsResponse response;
+
+    try
+    {
+        response = handler.getAncestors(path.c_str(), {boardInterface});
+    }
+    catch (const sdbusplus::exception_t& e)
+    {
+        error("Failed to get ancestors for path {PATH}: {ERROR}", "PATH", path,
+              "ERROR", e);
+        return std::nullopt;
+    }
+
+    if (response.empty())
+    {
+        error(
+            "Failed to get unique board path for Inventory path {PATH}, found: {SIZE}",
+            "PATH", path, "SIZE", response.size());
+        return std::nullopt;
+    }
+
+    return std::get<ObjectPath>(response.front());
+}
+
+std::optional<InventoryPath> FirmwareInventoryManager::getInventoryPath(
+    const pldm::eid& eid) const
+{
+    for (const auto& [configDbusPath, configMctpInfo] : configurations)
+    {
+        if (std::get<pldm::eid>(configMctpInfo) == eid)
+        {
+            return configDbusPath;
+        }
+    }
+    warning("No inventory path found for EID {EID}", "EID", eid);
+    return std::nullopt;
+}
+
+} // namespace pldm::fw_update
diff --git a/fw-update/firmware_inventory_manager.hpp b/fw-update/firmware_inventory_manager.hpp
new file mode 100644
index 0000000..e6616ce
--- /dev/null
+++ b/fw-update/firmware_inventory_manager.hpp
@@ -0,0 +1,101 @@
+#pragma once
+
+#include "common/types.hpp"
+#include "firmware_inventory.hpp"
+
+class FirmwareInventoryManagerTest;
+
+namespace pldm::fw_update
+{
+
+using ObjectPath = pldm::dbus::ObjectPath;
+using SoftwareMap =
+    std::map<SoftwareIdentifier, std::unique_ptr<FirmwareInventory>>;
+
+/**
+ * @brief Get the Board path from Entity Manager for the given inventory path
+ * @param[in] path - Inventory path
+ * @param[in] handler - D-Bus handler for querying ancestors
+ * @return Board path if found, std::nullopt otherwise
+ */
+std::optional<std::filesystem::path> getBoardPath(
+    const pldm::utils::DBusHandler& handler, const InventoryPath& path);
+
+class FirmwareInventoryManager
+{
+  public:
+    friend class ::FirmwareInventoryManagerTest;
+    FirmwareInventoryManager() = delete;
+    FirmwareInventoryManager(const FirmwareInventoryManager&) = delete;
+    FirmwareInventoryManager(FirmwareInventoryManager&&) = delete;
+    FirmwareInventoryManager& operator=(const FirmwareInventoryManager&) =
+        delete;
+    FirmwareInventoryManager& operator=(FirmwareInventoryManager&&) = delete;
+    ~FirmwareInventoryManager() = default;
+
+    /**
+     * @brief Constructor
+     * @param[in] configurations - Reference to the EM configurations for MCTP
+     * endpoints
+     */
+    explicit FirmwareInventoryManager(
+        const pldm::utils::DBusHandler* dbusHandler,
+        const Configurations& config) :
+        dbusHandler(dbusHandler), configurations(config)
+    {}
+
+    /**
+     * @brief Creates a firmware inventory entry for the given software
+     * identifier
+     * @param[in] softwareIdentifier - Software identifier containing EID and
+     *                                 component identifier
+     * @param[in] softwareName - Name of the firmware device
+     * @param[in] activeVersion - Active version of the firmware
+     * @param[in] descriptors - Descriptors associated with the firmware
+     * @param[in] componentInfo - Component information associated with the
+     * firmware
+     */
+    void createFirmwareEntry(
+        const SoftwareIdentifier& softwareIdentifier,
+        const SoftwareName& softwareName, const std::string& activeVersion,
+        const Descriptors& descriptors, const ComponentInfo& componentInfo);
+
+    /**
+     * @brief Deletes the firmware inventory entry for the given EID
+     * @param[in] eid - MCTP endpoint ID for which the firmware inventory entry
+     *                  needs to be deleted
+     */
+    void deleteFirmwareEntry(const pldm::eid& eid);
+
+  private:
+    /**
+     * @brief Get the inventory path associated with the given EID
+     * @param[in] eid - MCTP endpoint ID
+     * @return Inventory path if found, std::nullopt otherwise
+     */
+    std::optional<InventoryPath> getInventoryPath(const pldm::eid& eid) const;
+
+    /**
+     * @brief D-Bus Handler
+     */
+    const pldm::utils::DBusHandler* dbusHandler;
+
+    /**
+     * @brief Map of software identifier to FirmwareInventory instances
+     *        This map maintains the firmware inventory entries created for
+     *        different firmware components identified by their software
+     *        identifiers (EID and component identifier).
+     */
+    SoftwareMap softwareMap;
+
+    /**
+     * @brief Reference to the EM configurations for MCTP endpoints
+     *        This is used to retrieve the associated endpoint information
+     *        when creating firmware inventory entries.
+     *        It is expected that the configurations are provided during
+     *        the initialization of the FirmwareInventoryManager.
+     */
+    const Configurations& configurations;
+};
+
+} // namespace pldm::fw_update
diff --git a/fw-update/inventory_manager.cpp b/fw-update/inventory_manager.cpp
index 38e8392..6ad9f39 100644
--- a/fw-update/inventory_manager.cpp
+++ b/fw-update/inventory_manager.cpp
@@ -15,10 +15,11 @@
 {
 namespace fw_update
 {
-void InventoryManager::discoverFDs(const std::vector<mctp_eid_t>& eids)
+void InventoryManager::discoverFDs(const MctpInfos& mctpInfos)
 {
-    for (const auto& eid : eids)
+    for (const auto& mctpInfo : mctpInfos)
     {
+        auto eid = std::get<pldm::eid>(mctpInfo);
         try
         {
             sendQueryDeviceIdentifiersRequest(eid);
@@ -32,6 +33,19 @@
     }
 }
 
+void InventoryManager::removeFDs(const MctpInfos& mctpInfos)
+{
+    for (const auto& mctpInfo : mctpInfos)
+    {
+        auto eid = std::get<pldm::eid>(mctpInfo);
+        firmwareDeviceNameMap.erase(eid);
+        descriptorMap.erase(eid);
+        downstreamDescriptorMap.erase(eid);
+        componentInfoMap.erase(eid);
+        firmwareInventoryManager.deleteFirmwareEntry(eid);
+    }
+}
+
 void InventoryManager::sendQueryDeviceIdentifiersRequest(mctp_eid_t eid)
 {
     auto instanceId = instanceIdDb.next(eid);
@@ -161,7 +175,8 @@
         deviceIdentifiersLen -= nextDescriptorOffset;
     }
 
-    descriptorMap.emplace(eid, std::move(descriptors));
+    obtainFirmwareDeviceName(eid, descriptors);
+    descriptorMap.insert_or_assign(eid, std::move(descriptors));
 
     // Send GetFirmwareParameters request
     sendGetFirmwareParametersRequest(eid);
@@ -544,6 +559,25 @@
     }
 }
 
+void InventoryManager::obtainFirmwareDeviceName(pldm::eid eid,
+                                                const Descriptors& descriptors)
+{
+    auto firmwareDeviceName =
+        obtainDeviceNameFromConfigurations(configurations, eid);
+
+    if (!firmwareDeviceName)
+    {
+        firmwareDeviceName = obtainDeviceNameFromDescriptors(descriptors);
+    }
+
+    if (!firmwareDeviceName)
+    {
+        firmwareDeviceName = std::format("Firmware_Device_{}", eid);
+    }
+
+    firmwareDeviceNameMap.insert_or_assign(eid, *firmwareDeviceName);
+}
+
 void InventoryManager::sendGetFirmwareParametersRequest(mctp_eid_t eid)
 {
     auto instanceId = instanceIdDb.next(eid);
@@ -642,7 +676,62 @@
         compParamTableLen -= sizeof(pldm_component_parameter_entry) +
                              activeCompVerStr.length + pendingCompVerStr.length;
     }
-    componentInfoMap.emplace(eid, std::move(componentInfo));
+
+    if (firmwareDeviceNameMap.contains(eid))
+    {
+        firmwareInventoryManager.createFirmwareEntry(
+            SoftwareIdentifier(eid, 0), firmwareDeviceNameMap.at(eid),
+            utils::toString(activeCompImageSetVerStr), descriptorMap[eid],
+            componentInfo);
+    }
+    else
+    {
+        error("Firmware device name not found for endpoint ID {EID}", "EID",
+              eid);
+    }
+
+    componentInfoMap.insert_or_assign(eid, std::move(componentInfo));
+}
+
+std::optional<SoftwareName> obtainDeviceNameFromConfigurations(
+    const Configurations& configurations, pldm::eid eid)
+{
+    for (const auto& [_, mctpInfo] : configurations)
+    {
+        if (std::get<pldm::eid>(mctpInfo) == eid)
+        {
+            auto nameOption = std::get<std::optional<std::string>>(mctpInfo);
+            if (nameOption)
+            {
+                return *nameOption;
+            }
+            break;
+        }
+    }
+    return std::nullopt;
+}
+
+std::optional<SoftwareName> obtainDeviceNameFromDescriptors(
+    const Descriptors& descriptors)
+{
+    for (const auto& [descriptorType, descriptorData] : descriptors)
+    {
+        if (descriptorType == PLDM_FWUP_VENDOR_DEFINED)
+        {
+            auto vendorInfo =
+                std::get<VendorDefinedDescriptorInfo>(descriptorData);
+            auto title = std::get<VendorDefinedDescriptorTitle>(vendorInfo);
+            if (title == "OpenBMC.Name")
+            {
+                auto deviceNameData =
+                    std::get<VendorDefinedDescriptorData>(vendorInfo);
+                return SoftwareName{
+                    reinterpret_cast<char*>(deviceNameData.data()),
+                    deviceNameData.size()};
+            }
+        }
+    }
+    return std::nullopt;
 }
 
 } // namespace fw_update
diff --git a/fw-update/inventory_manager.hpp b/fw-update/inventory_manager.hpp
index ade1fcd..673b513 100644
--- a/fw-update/inventory_manager.hpp
+++ b/fw-update/inventory_manager.hpp
@@ -2,6 +2,7 @@
 
 #include "common/instance_id.hpp"
 #include "common/types.hpp"
+#include "firmware_inventory_manager.hpp"
 #include "requester/handler.hpp"
 
 namespace pldm
@@ -41,14 +42,17 @@
      *                                 managed by the BMC.
      */
     explicit InventoryManager(
+        const pldm::utils::DBusHandler* dbusHandler,
         pldm::requester::Handler<pldm::requester::Request>& handler,
         InstanceIdDb& instanceIdDb, DescriptorMap& descriptorMap,
         DownstreamDescriptorMap& downstreamDescriptorMap,
-        ComponentInfoMap& componentInfoMap) :
+        ComponentInfoMap& componentInfoMap,
+        const Configurations& configurations) :
         handler(handler), instanceIdDb(instanceIdDb),
         descriptorMap(descriptorMap),
         downstreamDescriptorMap(downstreamDescriptorMap),
-        componentInfoMap(componentInfoMap)
+        componentInfoMap(componentInfoMap), configurations(configurations),
+        firmwareInventoryManager(dbusHandler, configurations)
     {}
 
     /** @brief Discover the firmware identifiers and component details of FDs
@@ -57,9 +61,18 @@
      *  commands are sent to every FD and the response is used to populate
      *  the firmware identifiers and component details of the FDs.
      *
-     *  @param[in] eids - MCTP endpoint ID of the FDs
+     *  @param[in] mctpInfos - List of MCTP endpoint information
      */
-    void discoverFDs(const std::vector<mctp_eid_t>& eids);
+    void discoverFDs(const MctpInfos& mctpInfos);
+
+    /** @brief Remove the firmware identifiers and component details of FDs
+     *
+     *  This function removes the firmware identifiers, component details and
+     *  downstream device identifiers of the FDs managed by the BMC.
+     *
+     *  @param[in] mctpInfos - List of MCTP endpoint information
+     */
+    void removeFDs(const MctpInfos& mctpInfos);
 
     /** @brief Handler for QueryDeviceIdentifiers command response
      *
@@ -153,6 +166,17 @@
         mctp_eid_t eid, uint32_t dataTransferHandle,
         const enum transfer_op_flag transferOperationFlag);
 
+    /**
+     * @brief obtain Firmware Device Name from either configurations or
+     * descriptors, if none of them is available, a default name is
+     * generated.
+     *
+     * @param[in] eid - Remote MCTP endpoint
+     * @param[in] descriptors - Descriptors of the firmware device
+     */
+    void obtainFirmwareDeviceName(pldm::eid eid,
+                                  const Descriptors& descriptors);
+
     /** @brief Send GetFirmwareParameters command request
      *
      *  @param[in] eid - Remote MCTP endpoint
@@ -168,13 +192,43 @@
     /** @brief Device identifiers of the managed FDs */
     DescriptorMap& descriptorMap;
 
+    /** @brief Firmware Device names of the managed FDs */
+    std::map<eid, SoftwareName> firmwareDeviceNameMap;
+
     /** @brief Downstream Device identifiers of the managed FDs */
     DownstreamDescriptorMap& downstreamDescriptorMap;
 
     /** @brief Component information needed for the update of the managed FDs */
     ComponentInfoMap& componentInfoMap;
+
+    /** @brief Configuration bindings from Entity Manager */
+    const Configurations& configurations;
+
+    /** @brief Dbus Inventory Item Manager */
+    FirmwareInventoryManager firmwareInventoryManager;
 };
 
+/**
+ * @brief Obtain the device name from the configurations
+ *
+ * @param[in] configurations - Configurations from Entity Manager
+ * @param[in] eid - Remote MCTP endpoint
+ *
+ * @return SoftwareName - The Device name, std::nullopt if not found
+ */
+std::optional<SoftwareName> obtainDeviceNameFromConfigurations(
+    const Configurations& configurations, pldm::eid eid);
+
+/**
+ * @brief Obtain the device name from the descriptors
+ *
+ * @param[in] descriptors - Descriptors of the device
+ *
+ * @return SoftwareName - The Device name, std::nullopt if not found
+ */
+std::optional<SoftwareName> obtainDeviceNameFromDescriptors(
+    const Descriptors& descriptors);
+
 } // namespace fw_update
 
 } // namespace pldm
diff --git a/fw-update/manager.hpp b/fw-update/manager.hpp
index db5cb64..2e75f6c 100644
--- a/fw-update/manager.hpp
+++ b/fw-update/manager.hpp
@@ -33,15 +33,23 @@
     Manager& operator=(Manager&&) = delete;
     ~Manager() = default;
 
-    /** @brief Constructor
+    /**
+     * @brief Constructor for the PLDM Firmware Update Manager
      *
-     *  @param[in] handler - PLDM request handler
+     * @param[in] dbusHandler - Pointer to the D-Bus handler used for querying
+     * inventory and board paths
+     * @param[in] event - Reference to the io_context event object for
+     * asynchronous operations
+     * @param[in] handler - Reference to the PLDM request handler for processing
+     * PLDM requests
+     * @param[in] instanceIdDb - Reference to the InstanceId database for
+     * managing PLDM instance IDs
      */
-    explicit Manager(Event& event,
+    explicit Manager(const pldm::utils::DBusHandler* dbusHandler, Event& event,
                      requester::Handler<requester::Request>& handler,
                      pldm::InstanceIdDb& instanceIdDb) :
-        inventoryMgr(handler, instanceIdDb, descriptorMap,
-                     downstreamDescriptorMap, componentInfoMap),
+        inventoryMgr(dbusHandler, handler, instanceIdDb, descriptorMap,
+                     downstreamDescriptorMap, componentInfoMap, configurations),
         updateManager(event, handler, instanceIdDb, descriptorMap,
                       componentInfoMap)
     {}
@@ -51,15 +59,19 @@
      *
      *  @param[in] mctpInfos - information of discovered MCTP endpoints
      */
-    void handleMctpEndpoints(const MctpInfos& mctpInfos)
+    void handleMctpEndpoints(const MctpInfos& mctpInfos) override
     {
-        std::vector<mctp_eid_t> eids;
-        for (const auto& mctpInfo : mctpInfos)
-        {
-            eids.emplace_back(std::get<mctp_eid_t>(mctpInfo));
-        }
+        inventoryMgr.discoverFDs(mctpInfos);
+    }
 
-        inventoryMgr.discoverFDs(eids);
+    /** @brief Helper function to invoke registered handlers for
+     *         the updated EM configurations
+     *
+     *  @param[in] configurations - updated EM configurations
+     */
+    void handleConfigurations(const Configurations& configurations) override
+    {
+        this->configurations = configurations;
     }
 
     /** @brief Helper function to invoke registered handlers for
@@ -67,9 +79,9 @@
      *
      *  @param[in] mctpInfos - information of removed MCTP endpoints
      */
-    void handleRemovedMctpEndpoints(const MctpInfos&)
+    void handleRemovedMctpEndpoints(const MctpInfos& mctpInfos) override
     {
-        return;
+        inventoryMgr.removeFDs(mctpInfos);
     }
 
     /** @brief Helper function to invoke registered handlers for
@@ -78,7 +90,7 @@
      *  @param[in] mctpInfo - information of the target endpoint
      *  @param[in] availability - new availability status
      */
-    void updateMctpEndpointAvailability(const MctpInfo&, Availability)
+    void updateMctpEndpointAvailability(const MctpInfo&, Availability) override
     {
         return;
     }
@@ -103,7 +115,7 @@
      *  @param[in] addr - MCTP address of terminus
      *  @param[in] terminiNames - MCTP terminus name
      */
-    std::optional<mctp_eid_t> getActiveEidByName(const std::string&)
+    std::optional<mctp_eid_t> getActiveEidByName(const std::string&) override
     {
         return std::nullopt;
     }
@@ -119,6 +131,9 @@
     /** Component information of all the discovered MCTP endpoints */
     ComponentInfoMap componentInfoMap;
 
+    /** Configuration bindings from the Entity Manager */
+    Configurations configurations;
+
     /** @brief PLDM firmware inventory manager */
     InventoryManager inventoryMgr;
 
diff --git a/fw-update/test/firmware_inventory_manager_test.cpp b/fw-update/test/firmware_inventory_manager_test.cpp
new file mode 100644
index 0000000..4814838
--- /dev/null
+++ b/fw-update/test/firmware_inventory_manager_test.cpp
@@ -0,0 +1,90 @@
+#include "common/test/mocked_utils.hpp"
+#include "fw-update/firmware_inventory.hpp"
+#include "fw-update/firmware_inventory_manager.hpp"
+
+#include <gmock/gmock.h>
+#include <gtest/gtest.h>
+
+using namespace pldm;
+using namespace std::chrono;
+using namespace pldm::fw_update;
+
+// Helper class for testing: inherits FirmwareInventory and exposes protected
+class FirmwareInventoryTest : public pldm::fw_update::FirmwareInventory
+{
+  public:
+    using FirmwareInventory::FirmwareInventory;
+
+    const std::string& getSoftwarePath() const
+    {
+        return this->softwarePath;
+    }
+    const SoftwareAssociationDefinitions& getAssociation() const
+    {
+        return this->association;
+    }
+    const SoftwareVersion& getVersion() const
+    {
+        return this->version;
+    }
+};
+
+class FirmwareInventoryManagerTest : public FirmwareInventoryManager
+{
+  public:
+    FirmwareInventoryManagerTest(const pldm::utils::DBusHandler* handler,
+                                 const Configurations& config) :
+        FirmwareInventoryManager(handler, config)
+    {}
+
+    SoftwareMap& getSoftwareMap()
+    {
+        return softwareMap;
+    }
+};
+
+TEST(GetBoardPath_WithMockHandler, ReturnsExpectedBoardPath)
+{
+    MockdBusHandler mockHandler;
+    InventoryPath inventoryPath =
+        "/xyz/openbmc_project/inventory/system/board/PLDM_Device";
+    pldm::utils::GetAncestorsResponse fakeResponse = {{inventoryPath, {}}};
+    EXPECT_CALL(mockHandler, getAncestors)
+        .WillOnce(::testing::Return(fakeResponse));
+
+    Configurations configurations;
+    std::string boardInventoryPath =
+        "/xyz/openbmc_project/inventory/system/board/PLDM_Device";
+    pldm::eid endpointId = 1;
+    pldm::UUID endpointUuid = "uuid";
+    pldm::MctpMedium endpointMedium = "medium";
+    pldm::NetworkId endpointNetId = 0;
+    pldm::MctpInfoName endpointName = "BMC";
+    pldm::MctpInfo endpointInfo = std::make_tuple(
+        endpointId, endpointUuid, endpointMedium, endpointNetId, endpointName);
+    configurations[boardInventoryPath] = endpointInfo;
+
+    FirmwareInventoryManagerTest inventoryManager(&mockHandler, configurations);
+
+    SoftwareIdentifier softwareIdentifier{endpointId, 100};
+    SoftwareName softwareName{"TestDevice"};
+    std::string firmwareVersion{"1.0.0"};
+    Descriptors firmwareDescriptors;
+    ComponentInfo firmwareComponentInfo;
+
+    inventoryManager.createFirmwareEntry(
+        softwareIdentifier, softwareName, firmwareVersion, firmwareDescriptors,
+        firmwareComponentInfo);
+    ASSERT_TRUE(inventoryManager.getSoftwareMap().contains(softwareIdentifier));
+
+    auto inventoryIt =
+        inventoryManager.getSoftwareMap().find(softwareIdentifier);
+    ASSERT_NE(inventoryIt, inventoryManager.getSoftwareMap().end());
+    const auto* inventory =
+        static_cast<FirmwareInventoryTest*>(inventoryIt->second.get());
+    ASSERT_NE(inventory, nullptr);
+    EXPECT_NE(inventory->getSoftwarePath().find(
+                  "/xyz/openbmc_project/software/PLDM_Device_TestDevice_"),
+              std::string::npos);
+    EXPECT_EQ(inventory->getVersion().version(), firmwareVersion);
+}
diff --git a/fw-update/test/firmware_inventory_test.cpp b/fw-update/test/firmware_inventory_test.cpp
new file mode 100644
index 0000000..46611bd
--- /dev/null
+++ b/fw-update/test/firmware_inventory_test.cpp
@@ -0,0 +1,50 @@
+#include "fw-update/firmware_inventory.hpp"
+
+#include <string>
+
+#include <gtest/gtest.h>
+
+using namespace pldm::fw_update;
+
+class FirmwareInventoryTest : public FirmwareInventory
+{
+  public:
+    using FirmwareInventory::FirmwareInventory;
+    const std::string& getSoftwarePath() const
+    {
+        return this->softwarePath;
+    }
+    const SoftwareAssociationDefinitions& getAssociation() const
+    {
+        return this->association;
+    }
+    const SoftwareVersion& getVersion() const
+    {
+        return this->version;
+    }
+};
+
+TEST(FirmwareInventoryTest, ConstructorSetsProperties)
+{
+    SoftwareIdentifier softwareIdentifier{1, 100};
+    std::string expectedSoftwarePath =
+        "/xyz/openbmc_project/software/PLDM_Device_TestDevice_1234";
+    std::string expectedSoftwareVersion = "2.3.4";
+    std::string expectedEndpointPath =
+        "/xyz/openbmc_project/inventory/system/board/PLDM_Device";
+    Descriptors firmwareDescriptors;
+    ComponentInfo firmwareComponentInfo;
+    SoftwareVersionPurpose expectedPurpose = SoftwareVersionPurpose::Unknown;
+
+    FirmwareInventoryTest inventory(
+        softwareIdentifier, expectedSoftwarePath, expectedSoftwareVersion,
+        expectedEndpointPath, firmwareDescriptors, firmwareComponentInfo,
+        expectedPurpose);
+
+    EXPECT_EQ(inventory.getSoftwarePath(), expectedSoftwarePath);
+    auto associationTuples = inventory.getAssociation().associations();
+    ASSERT_FALSE(associationTuples.empty());
+    EXPECT_EQ(std::get<2>(associationTuples[0]), expectedEndpointPath);
+    EXPECT_EQ(inventory.getVersion().version(), expectedSoftwareVersion);
+    EXPECT_EQ(inventory.getVersion().purpose(), expectedPurpose);
+}
diff --git a/fw-update/test/inventory_manager_test.cpp b/fw-update/test/inventory_manager_test.cpp
index a768ab2..f2fd789 100644
--- a/fw-update/test/inventory_manager_test.cpp
+++ b/fw-update/test/inventory_manager_test.cpp
@@ -18,11 +18,13 @@
         event(sdeventplus::Event::get_default()), instanceIdDb(),
         reqHandler(nullptr, event, instanceIdDb, false, seconds(1), 2,
                    milliseconds(100)),
-        inventoryManager(reqHandler, instanceIdDb, outDescriptorMap,
-                         outDownstreamDescriptorMap, outComponentInfoMap)
+        inventoryManager(&dBusHandler, reqHandler, instanceIdDb,
+                         outDescriptorMap, outDownstreamDescriptorMap,
+                         outComponentInfoMap, configurations)
     {}
 
     int fd = -1;
+    const pldm::utils::DBusHandler dBusHandler;
     sdeventplus::Event event;
     TestInstanceIdDb instanceIdDb;
     requester::Handler<requester::Request> reqHandler;
@@ -30,6 +32,7 @@
     DescriptorMap outDescriptorMap{};
     DownstreamDescriptorMap outDownstreamDescriptorMap{};
     ComponentInfoMap outComponentInfoMap{};
+    Configurations configurations;
 };
 
 TEST_F(InventoryManagerTest, handleQueryDeviceIdentifiersResponse)
diff --git a/fw-update/test/meson.build b/fw-update/test/meson.build
index e419ccf..ba78fcd 100644
--- a/fw-update/test/meson.build
+++ b/fw-update/test/meson.build
@@ -5,11 +5,19 @@
         '../package_parser.cpp',
         '../device_updater.cpp',
         '../update_manager.cpp',
+        '../firmware_inventory_manager.cpp',
+        '../firmware_inventory.cpp',
         '../../common/utils.cpp',
     ],
 )
 
-tests = ['inventory_manager_test', 'package_parser_test', 'device_updater_test']
+tests = [
+    'inventory_manager_test',
+    'package_parser_test',
+    'device_updater_test',
+    'firmware_inventory_manager_test',
+    'firmware_inventory_test',
+]
 
 foreach t : tests
     test(
diff --git a/meson.build b/meson.build
index ddf9efc..461d657 100644
--- a/meson.build
+++ b/meson.build
@@ -248,6 +248,8 @@
     'pldmd/dbus_impl_pdr.cpp',
     'fw-update/activation.cpp',
     'fw-update/inventory_manager.cpp',
+    'fw-update/firmware_inventory_manager.cpp',
+    'fw-update/firmware_inventory.cpp',
     'fw-update/package_parser.cpp',
     'fw-update/device_updater.cpp',
     'fw-update/watch.cpp',
diff --git a/pldmd/pldmd.cpp b/pldmd/pldmd.cpp
index 00da6b2..ba448e6 100644
--- a/pldmd/pldmd.cpp
+++ b/pldmd/pldmd.cpp
@@ -341,7 +341,8 @@
 #endif
 
     std::unique_ptr<fw_update::Manager> fwManager =
-        std::make_unique<fw_update::Manager>(event, reqHandler, instanceIdDb);
+        std::make_unique<fw_update::Manager>(&dbusHandler, event, reqHandler,
+                                             instanceIdDb);
     std::unique_ptr<MctpDiscovery> mctpDiscoveryHandler =
         std::make_unique<MctpDiscovery>(
             bus, std::initializer_list<MctpDiscoveryHandlerIntf*>{