fw-update: Implement inventory manager

1. Discover firmware devices that implement fw update specification
2. Implements request/response for QueryDeviceIdentifiers command
3. Implements request/response for GetFirmwareParameters command
4. Enumerates device identifiers and component information to be used
   for fw-update

Signed-off-by: Tom Joseph <rushtotom@gmail.com>
Change-Id: Ifa035c801a7c62bac9a7e947ed4a43d48f85a4ed
diff --git a/common/types.hpp b/common/types.hpp
index 865f2c4..c6d7734 100644
--- a/common/types.hpp
+++ b/common/types.hpp
@@ -2,14 +2,18 @@
 
 #include <stdint.h>
 
+#include <bitset>
+#include <map>
 #include <set>
 #include <string>
+#include <unordered_map>
 #include <variant>
 #include <vector>
 
 namespace pldm
 {
 
+using eid = uint8_t;
 using Request = std::vector<uint8_t>;
 using Response = std::vector<uint8_t>;
 
@@ -27,6 +31,32 @@
 
 } // namespace dbus
 
+namespace fw_update
+{
+
+// Descriptor definition
+using DescriptorType = uint16_t;
+using DescriptorData = std::vector<uint8_t>;
+using VendorDefinedDescriptorTitle = std::string;
+using VendorDefinedDescriptorData = std::vector<uint8_t>;
+using VendorDefinedDescriptorInfo =
+    std::tuple<VendorDefinedDescriptorTitle, VendorDefinedDescriptorData>;
+using Descriptors =
+    std::map<DescriptorType,
+             std::variant<DescriptorData, VendorDefinedDescriptorInfo>>;
+
+using DescriptorMap = std::unordered_map<eid, Descriptors>;
+
+// Component information
+using CompClassification = uint16_t;
+using CompIdentifier = uint16_t;
+using CompKey = std::pair<CompClassification, CompIdentifier>;
+using CompClassificationIndex = uint8_t;
+using ComponentInfo = std::map<CompKey, CompClassificationIndex>;
+using ComponentInfoMap = std::unordered_map<eid, ComponentInfo>;
+
+} // namespace fw_update
+
 namespace pdr
 {
 
diff --git a/fw-update/inventory_manager.cpp b/fw-update/inventory_manager.cpp
new file mode 100644
index 0000000..c0397c2
--- /dev/null
+++ b/fw-update/inventory_manager.cpp
@@ -0,0 +1,245 @@
+#include "inventory_manager.hpp"
+
+#include "libpldm/firmware_update.h"
+
+#include "common/utils.hpp"
+#include "xyz/openbmc_project/Software/Version/server.hpp"
+
+#include <functional>
+
+namespace pldm
+{
+
+namespace fw_update
+{
+
+void InventoryManager::discoverFDs(const std::vector<mctp_eid_t>& eids)
+{
+    for (const auto& eid : eids)
+    {
+        auto instanceId = requester.getInstanceId(eid);
+        Request requestMsg(sizeof(pldm_msg_hdr) +
+                           PLDM_QUERY_DEVICE_IDENTIFIERS_REQ_BYTES);
+        auto request = reinterpret_cast<pldm_msg*>(requestMsg.data());
+        auto rc = encode_query_device_identifiers_req(
+            instanceId, PLDM_QUERY_DEVICE_IDENTIFIERS_REQ_BYTES, request);
+        if (rc)
+        {
+            requester.markFree(eid, instanceId);
+            std::cerr << "encode_query_device_identifiers_req failed, EID="
+                      << unsigned(eid) << ", RC=" << rc << "\n";
+            continue;
+        }
+
+        rc = handler.registerRequest(
+            eid, instanceId, PLDM_FWUP, PLDM_QUERY_DEVICE_IDENTIFIERS,
+            std::move(requestMsg),
+            std::move(std::bind_front(&InventoryManager::queryDeviceIdentifiers,
+                                      this)));
+        if (rc)
+        {
+            std::cerr << "Failed to send QueryDeviceIdentifiers request, EID="
+                      << unsigned(eid) << ", RC=" << rc << "\n ";
+        }
+    }
+}
+
+void InventoryManager::queryDeviceIdentifiers(mctp_eid_t eid,
+                                              const pldm_msg* response,
+                                              size_t respMsgLen)
+{
+    if (response == nullptr || !respMsgLen)
+    {
+        std::cerr << "No response received for QueryDeviceIdentifiers, EID="
+                  << unsigned(eid) << "\n";
+        return;
+    }
+
+    uint8_t completionCode = PLDM_SUCCESS;
+    uint32_t deviceIdentifiersLen = 0;
+    uint8_t descriptorCount = 0;
+    uint8_t* descriptorPtr = nullptr;
+
+    auto rc = decode_query_device_identifiers_resp(
+        response, respMsgLen, &completionCode, &deviceIdentifiersLen,
+        &descriptorCount, &descriptorPtr);
+    if (rc)
+    {
+        std::cerr << "Decoding QueryDeviceIdentifiers response failed, EID="
+                  << unsigned(eid) << ", RC=" << rc << "\n";
+        return;
+    }
+
+    if (completionCode)
+    {
+        std::cerr << "QueryDeviceIdentifiers response failed with error "
+                     "completion code, EID="
+                  << unsigned(eid) << ", CC=" << unsigned(completionCode)
+                  << "\n";
+        return;
+    }
+
+    Descriptors descriptors{};
+    while (descriptorCount-- && (deviceIdentifiersLen > 0))
+    {
+        uint16_t descriptorType = 0;
+        variable_field descriptorData{};
+
+        rc = decode_descriptor_type_length_value(
+            descriptorPtr, deviceIdentifiersLen, &descriptorType,
+            &descriptorData);
+        if (rc)
+        {
+            std::cerr
+                << "Decoding descriptor type, length and value failed, EID="
+                << unsigned(eid) << ", RC=" << rc << "\n ";
+            return;
+        }
+
+        if (descriptorType != PLDM_FWUP_VENDOR_DEFINED)
+        {
+            std::vector<uint8_t> descData(
+                descriptorData.ptr, descriptorData.ptr + descriptorData.length);
+            descriptors.emplace(descriptorType, std::move(descData));
+        }
+        else
+        {
+            uint8_t descriptorTitleStrType = 0;
+            variable_field descriptorTitleStr{};
+            variable_field vendorDefinedDescriptorData{};
+
+            rc = decode_vendor_defined_descriptor_value(
+                descriptorData.ptr, descriptorData.length,
+                &descriptorTitleStrType, &descriptorTitleStr,
+                &vendorDefinedDescriptorData);
+            if (rc)
+            {
+                std::cerr
+                    << "Decoding Vendor-defined descriptor value failed, EID="
+                    << unsigned(eid) << ", RC=" << rc << "\n ";
+                return;
+            }
+
+            auto vendorDefinedDescriptorTitleStr =
+                utils::toString(descriptorTitleStr);
+            std::vector<uint8_t> vendorDescData(
+                vendorDefinedDescriptorData.ptr,
+                vendorDefinedDescriptorData.ptr +
+                    vendorDefinedDescriptorData.length);
+            descriptors.emplace(descriptorType,
+                                std::make_tuple(vendorDefinedDescriptorTitleStr,
+                                                vendorDescData));
+        }
+        auto nextDescriptorOffset =
+            sizeof(pldm_descriptor_tlv().descriptor_type) +
+            sizeof(pldm_descriptor_tlv().descriptor_length) +
+            descriptorData.length;
+        descriptorPtr += nextDescriptorOffset;
+        deviceIdentifiersLen -= nextDescriptorOffset;
+    }
+
+    descriptorMap.emplace(eid, std::move(descriptors));
+
+    // Send GetFirmwareParameters request
+    sendGetFirmwareParametersRequest(eid);
+}
+
+void InventoryManager::sendGetFirmwareParametersRequest(mctp_eid_t eid)
+{
+    auto instanceId = requester.getInstanceId(eid);
+    Request requestMsg(sizeof(pldm_msg_hdr) +
+                       PLDM_GET_FIRMWARE_PARAMETERS_REQ_BYTES);
+    auto request = reinterpret_cast<pldm_msg*>(requestMsg.data());
+    auto rc = encode_get_firmware_parameters_req(
+        instanceId, PLDM_GET_FIRMWARE_PARAMETERS_REQ_BYTES, request);
+    if (rc)
+    {
+        requester.markFree(eid, instanceId);
+        std::cerr << "encode_get_firmware_parameters_req failed, EID="
+                  << unsigned(eid) << ", RC=" << rc << "\n";
+        return;
+    }
+
+    rc = handler.registerRequest(
+        eid, instanceId, PLDM_FWUP, PLDM_GET_FIRMWARE_PARAMETERS,
+        std::move(requestMsg),
+        std::move(
+            std::bind_front(&InventoryManager::getFirmwareParameters, this)));
+    if (rc)
+    {
+        std::cerr << "Failed to send GetFirmwareParameters request, EID="
+                  << unsigned(eid) << ", RC=" << rc << "\n ";
+    }
+}
+
+void InventoryManager::getFirmwareParameters(mctp_eid_t eid,
+                                             const pldm_msg* response,
+                                             size_t respMsgLen)
+{
+    if (response == nullptr || !respMsgLen)
+    {
+        std::cerr << "No response received for GetFirmwareParameters, EID="
+                  << unsigned(eid) << "\n";
+        descriptorMap.erase(eid);
+        return;
+    }
+
+    pldm_get_firmware_parameters_resp fwParams{};
+    variable_field activeCompImageSetVerStr{};
+    variable_field pendingCompImageSetVerStr{};
+    variable_field compParamTable{};
+
+    auto rc = decode_get_firmware_parameters_resp(
+        response, respMsgLen, &fwParams, &activeCompImageSetVerStr,
+        &pendingCompImageSetVerStr, &compParamTable);
+    if (rc)
+    {
+        std::cerr << "Decoding GetFirmwareParameters response failed, EID="
+                  << unsigned(eid) << ", RC=" << rc << "\n";
+        return;
+    }
+
+    if (fwParams.completion_code)
+    {
+        std::cerr << "GetFirmwareParameters response failed with error "
+                     "completion code, EID="
+                  << unsigned(eid)
+                  << ", CC=" << unsigned(fwParams.completion_code) << "\n";
+        return;
+    }
+
+    auto compParamPtr = compParamTable.ptr;
+    auto compParamTableLen = compParamTable.length;
+    pldm_component_parameter_entry compEntry{};
+    variable_field activeCompVerStr{};
+    variable_field pendingCompVerStr{};
+
+    ComponentInfo componentInfo{};
+    while (fwParams.comp_count-- && (compParamTableLen > 0))
+    {
+        auto rc = decode_get_firmware_parameters_resp_comp_entry(
+            compParamPtr, compParamTableLen, &compEntry, &activeCompVerStr,
+            &pendingCompVerStr);
+        if (rc)
+        {
+            std::cerr << "Decoding component parameter table entry failed, EID="
+                      << unsigned(eid) << ", RC=" << rc << "\n";
+            return;
+        }
+
+        auto compClassification = compEntry.comp_classification;
+        auto compIdentifier = compEntry.comp_identifier;
+        componentInfo.emplace(
+            std::make_pair(compClassification, compIdentifier),
+            compEntry.comp_classification_index);
+        compParamPtr += sizeof(pldm_component_parameter_entry) +
+                        activeCompVerStr.length + pendingCompVerStr.length;
+        compParamTableLen -= sizeof(pldm_component_parameter_entry) +
+                             activeCompVerStr.length + pendingCompVerStr.length;
+    }
+    componentInfoMap.emplace(eid, std::move(componentInfo));
+}
+
+} // namespace fw_update
+
+} // namespace pldm
\ No newline at end of file
diff --git a/fw-update/inventory_manager.hpp b/fw-update/inventory_manager.hpp
new file mode 100644
index 0000000..a14d58c
--- /dev/null
+++ b/fw-update/inventory_manager.hpp
@@ -0,0 +1,108 @@
+#pragma once
+
+#include "libpldm/requester/pldm.h"
+
+#include "common/types.hpp"
+#include "pldmd/dbus_impl_requester.hpp"
+#include "requester/handler.hpp"
+
+namespace pldm
+{
+
+namespace fw_update
+{
+
+/** @class InventoryManager
+ *
+ *  InventoryManager class manages the software inventory of firmware devices
+ *  managed by the BMC. It discovers the firmware identifiers and the component
+ *  details of the FD. Firmware identifiers, component details and update
+ *  capabilities of FD are populated by the InventoryManager and is used for the
+ *  firmware update of the FDs.
+ */
+class InventoryManager
+{
+  public:
+    InventoryManager() = delete;
+    InventoryManager(const InventoryManager&) = delete;
+    InventoryManager(InventoryManager&&) = delete;
+    InventoryManager& operator=(const InventoryManager&) = delete;
+    InventoryManager& operator=(InventoryManager&&) = delete;
+    ~InventoryManager() = default;
+
+    /** @brief Constructor
+     *
+     *  @param[in] handler - PLDM request handler
+     *  @param[in] requester - Managing instance ID for PLDM requests
+     *  @param[out] descriptorMap - Populate the firmware identifers for the
+     *                              FDs managed by the BMC.
+     *  @param[out] componentInfoMap - Populate the component info for the FDs
+     *                                 managed by the BMC.
+     */
+    explicit InventoryManager(
+        pldm::requester::Handler<pldm::requester::Request>& handler,
+        pldm::dbus_api::Requester& requester, DescriptorMap& descriptorMap,
+        ComponentInfoMap& componentInfoMap) :
+        handler(handler),
+        requester(requester), descriptorMap(descriptorMap),
+        componentInfoMap(componentInfoMap)
+    {}
+
+    /** @brief Discover the firmware identifiers and component details of FDs
+     *
+     *  Inventory commands QueryDeviceIdentifiers and GetFirmwareParmeters
+     *  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
+     */
+    void discoverFDs(const std::vector<mctp_eid_t>& eids);
+
+    /** @brief Handler for QueryDeviceIdentifiers command response
+     *
+     *  The response of the QueryDeviceIdentifiers is processed and firmware
+     *  identifiers of the FD is updated. GetFirmwareParameters command request
+     *  is sent to the FD.
+     *
+     *  @param[in] eid - Remote MCTP endpoint
+     *  @param[in] response - PLDM response message
+     *  @param[in] respMsgLen - Response message length
+     */
+    void queryDeviceIdentifiers(mctp_eid_t eid, const pldm_msg* response,
+                                size_t respMsgLen);
+
+    /** @brief Handler for GetFirmwareParameters command response
+     *
+     *  Handling the response of GetFirmwareParameters command and create
+     *  software version D-Bus objects.
+     *
+     *  @param[in] eid - Remote MCTP endpoint
+     *  @param[in] response - PLDM response message
+     *  @param[in] respMsgLen - Response message length
+     */
+    void getFirmwareParameters(mctp_eid_t eid, const pldm_msg* response,
+                               size_t respMsgLen);
+
+  private:
+    /** @brief Send GetFirmwareParameters command request
+     *
+     *  @param[in] eid - Remote MCTP endpoint
+     */
+    void sendGetFirmwareParametersRequest(mctp_eid_t eid);
+
+    /** @brief PLDM request handler */
+    pldm::requester::Handler<pldm::requester::Request>& handler;
+
+    /** @brief D-Bus API for managing instance ID*/
+    pldm::dbus_api::Requester& requester;
+
+    /** @brief Device identifiers of the managed FDs */
+    DescriptorMap& descriptorMap;
+
+    /** @brief Component information needed for the update of the managed FDs */
+    ComponentInfoMap& componentInfoMap;
+};
+
+} // namespace fw_update
+
+} // namespace pldm
diff --git a/fw-update/test/inventory_manager_test.cpp b/fw-update/test/inventory_manager_test.cpp
new file mode 100644
index 0000000..5b14aac
--- /dev/null
+++ b/fw-update/test/inventory_manager_test.cpp
@@ -0,0 +1,188 @@
+#include "libpldm/firmware_update.h"
+
+#include "common/utils.hpp"
+#include "fw-update/inventory_manager.hpp"
+#include "requester/test/mock_request.hpp"
+
+#include <gtest/gtest.h>
+
+using namespace pldm;
+using namespace std::chrono;
+using namespace pldm::fw_update;
+
+class InventoryManagerTest : public testing::Test
+{
+  protected:
+    InventoryManagerTest() :
+        event(sdeventplus::Event::get_default()),
+        dbusImplRequester(pldm::utils::DBusHandler::getBus(),
+                          "/xyz/openbmc_project/pldm"),
+        reqHandler(fd, event, dbusImplRequester, false, seconds(1), 2,
+                   milliseconds(100)),
+        inventoryManager(reqHandler, dbusImplRequester, outDescriptorMap,
+                         outComponentInfoMap)
+    {}
+
+    int fd = -1;
+    sdeventplus::Event event;
+    pldm::dbus_api::Requester dbusImplRequester;
+    requester::Handler<requester::Request> reqHandler;
+    InventoryManager inventoryManager;
+    DescriptorMap outDescriptorMap{};
+    ComponentInfoMap outComponentInfoMap{};
+};
+
+TEST_F(InventoryManagerTest, handleQueryDeviceIdentifiersResponse)
+{
+    constexpr size_t respPayloadLength1 = 49;
+    constexpr std::array<uint8_t, sizeof(pldm_msg_hdr) + respPayloadLength1>
+        queryDeviceIdentifiersResp1{
+            0x00, 0x00, 0x00, 0x00, 0x2b, 0x00, 0x00, 0x00, 0x03, 0x01, 0x00,
+            0x04, 0x00, 0x0a, 0x0b, 0x0c, 0x0d, 0x02, 0x00, 0x10, 0x00, 0x12,
+            0x44, 0xd2, 0x64, 0x8d, 0x7d, 0x47, 0x18, 0xa0, 0x30, 0xfc, 0x8a,
+            0x56, 0x58, 0x7d, 0x5b, 0xFF, 0xFF, 0x0B, 0x00, 0x01, 0x07, 0x4f,
+            0x70, 0x65, 0x6e, 0x42, 0x4d, 0x43, 0x01, 0x02};
+    auto responseMsg1 =
+        reinterpret_cast<const pldm_msg*>(queryDeviceIdentifiersResp1.data());
+    inventoryManager.queryDeviceIdentifiers(1, responseMsg1,
+                                            respPayloadLength1);
+
+    DescriptorMap descriptorMap1{
+        {0x01,
+         {{PLDM_FWUP_IANA_ENTERPRISE_ID,
+           std::vector<uint8_t>{0x0a, 0x0b, 0x0c, 0xd}},
+          {PLDM_FWUP_UUID,
+           std::vector<uint8_t>{0x12, 0x44, 0xd2, 0x64, 0x8d, 0x7d, 0x47, 0x18,
+                                0xa0, 0x30, 0xfc, 0x8a, 0x56, 0x58, 0x7d,
+                                0x5b}},
+          {PLDM_FWUP_VENDOR_DEFINED,
+           std::make_tuple("OpenBMC", std::vector<uint8_t>{0x01, 0x02})}}}};
+
+    EXPECT_EQ(outDescriptorMap.size(), descriptorMap1.size());
+    EXPECT_EQ(outDescriptorMap, descriptorMap1);
+
+    constexpr size_t respPayloadLength2 = 26;
+    constexpr std::array<uint8_t, sizeof(pldm_msg_hdr) + respPayloadLength2>
+        queryDeviceIdentifiersResp2{
+            0x00, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00, 0x01, 0x02,
+            0x00, 0x10, 0x00, 0xF0, 0x18, 0x87, 0x8C, 0xCB, 0x7D, 0x49,
+            0x43, 0x98, 0x00, 0xA0, 0x2F, 0x59, 0x9A, 0xCA, 0x02};
+    auto responseMsg2 =
+        reinterpret_cast<const pldm_msg*>(queryDeviceIdentifiersResp2.data());
+    inventoryManager.queryDeviceIdentifiers(2, responseMsg2,
+                                            respPayloadLength2);
+    DescriptorMap descriptorMap2{
+        {0x01,
+         {{PLDM_FWUP_IANA_ENTERPRISE_ID,
+           std::vector<uint8_t>{0x0a, 0x0b, 0x0c, 0xd}},
+          {PLDM_FWUP_UUID,
+           std::vector<uint8_t>{0x12, 0x44, 0xd2, 0x64, 0x8d, 0x7d, 0x47, 0x18,
+                                0xa0, 0x30, 0xfc, 0x8a, 0x56, 0x58, 0x7d,
+                                0x5b}},
+          {PLDM_FWUP_VENDOR_DEFINED,
+           std::make_tuple("OpenBMC", std::vector<uint8_t>{0x01, 0x02})}}},
+        {0x02,
+         {{PLDM_FWUP_UUID,
+           std::vector<uint8_t>{0xF0, 0x18, 0x87, 0x8C, 0xCB, 0x7D, 0x49, 0x43,
+                                0x98, 0x00, 0xA0, 0x2F, 0x59, 0x9A, 0xCA,
+                                0x02}}}}};
+    EXPECT_EQ(outDescriptorMap.size(), descriptorMap2.size());
+    EXPECT_EQ(outDescriptorMap, descriptorMap2);
+}
+
+TEST_F(InventoryManagerTest, handleQueryDeviceIdentifiersResponseErrorCC)
+{
+    constexpr size_t respPayloadLength = 1;
+    constexpr std::array<uint8_t, sizeof(pldm_msg_hdr) + respPayloadLength>
+        queryDeviceIdentifiersResp{0x00, 0x00, 0x00, 0x01};
+    auto responseMsg =
+        reinterpret_cast<const pldm_msg*>(queryDeviceIdentifiersResp.data());
+    inventoryManager.queryDeviceIdentifiers(1, responseMsg, respPayloadLength);
+    EXPECT_EQ(outDescriptorMap.size(), 0);
+}
+
+TEST_F(InventoryManagerTest, getFirmwareParametersResponse)
+{
+    // constexpr uint16_t compCount = 2;
+    // constexpr std::string_view activeCompImageSetVersion{"DeviceVer1.0"};
+    // constexpr std::string_view activeCompVersion1{"Comp1v2.0"};
+    // constexpr std::string_view activeCompVersion2{"Comp2v3.0"};
+    constexpr uint16_t compClassification1 = 10;
+    constexpr uint16_t compIdentifier1 = 300;
+    constexpr uint8_t compClassificationIndex1 = 20;
+    constexpr uint16_t compClassification2 = 16;
+    constexpr uint16_t compIdentifier2 = 301;
+    constexpr uint8_t compClassificationIndex2 = 30;
+
+    constexpr size_t respPayloadLength1 = 119;
+    constexpr std::array<uint8_t, sizeof(pldm_msg_hdr) + respPayloadLength1>
+        getFirmwareParametersResp1{
+            0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x01,
+            0x0c, 0x00, 0x00, 0x44, 0x65, 0x76, 0x69, 0x63, 0x65, 0x56, 0x65,
+            0x72, 0x31, 0x2e, 0x30, 0x0a, 0x00, 0x2c, 0x01, 0x14, 0x00, 0x00,
+            0x00, 0x00, 0x01, 0x09, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+            0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+            0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x43,
+            0x6f, 0x6d, 0x70, 0x31, 0x76, 0x32, 0x2e, 0x30, 0x10, 0x00, 0x2d,
+            0x01, 0x1E, 0x00, 0x00, 0x00, 0x00, 0x01, 0x09, 0x00, 0x00, 0x00,
+            0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+            0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00,
+            0x00, 0x00, 0x00, 0x43, 0x6f, 0x6d, 0x70, 0x32, 0x76, 0x33, 0x2e,
+            0x30};
+    auto responseMsg1 =
+        reinterpret_cast<const pldm_msg*>(getFirmwareParametersResp1.data());
+    inventoryManager.getFirmwareParameters(1, responseMsg1, respPayloadLength1);
+
+    ComponentInfoMap componentInfoMap1{
+        {1,
+         {{std::make_pair(compClassification1, compIdentifier1),
+           compClassificationIndex1},
+          {std::make_pair(compClassification2, compIdentifier2),
+           compClassificationIndex2}}}};
+    EXPECT_EQ(outComponentInfoMap.size(), componentInfoMap1.size());
+    EXPECT_EQ(outComponentInfoMap, componentInfoMap1);
+
+    // constexpr uint16_t compCount = 1;
+    // constexpr std::string_view activeCompImageSetVersion{"DeviceVer2.0"};
+    // constexpr std::string_view activeCompVersion1{"Comp3v4.0"};
+    constexpr uint16_t compClassification3 = 2;
+    constexpr uint16_t compIdentifier3 = 302;
+    constexpr uint8_t compClassificationIndex3 = 40;
+
+    constexpr size_t respPayloadLength2 = 119;
+    constexpr std::array<uint8_t, sizeof(pldm_msg_hdr) + respPayloadLength2>
+        getFirmwareParametersResp2{
+            0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x01,
+            0x0c, 0x00, 0x00, 0x44, 0x65, 0x76, 0x69, 0x63, 0x65, 0x56, 0x65,
+            0x72, 0x32, 0x2e, 0x30, 0x02, 0x00, 0x2e, 0x01, 0x28, 0x00, 0x00,
+            0x00, 0x00, 0x01, 0x09, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+            0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+            0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x43,
+            0x6f, 0x6d, 0x70, 0x33, 0x76, 0x34, 0x2e, 0x30};
+    auto responseMsg2 =
+        reinterpret_cast<const pldm_msg*>(getFirmwareParametersResp2.data());
+    inventoryManager.getFirmwareParameters(2, responseMsg2, respPayloadLength2);
+
+    ComponentInfoMap componentInfoMap2{
+        {1,
+         {{std::make_pair(compClassification1, compIdentifier1),
+           compClassificationIndex1},
+          {std::make_pair(compClassification2, compIdentifier2),
+           compClassificationIndex2}}},
+        {2,
+         {{std::make_pair(compClassification3, compIdentifier3),
+           compClassificationIndex3}}}};
+    EXPECT_EQ(outComponentInfoMap.size(), componentInfoMap2.size());
+    EXPECT_EQ(outComponentInfoMap, componentInfoMap2);
+}
+
+TEST_F(InventoryManagerTest, getFirmwareParametersResponseErrorCC)
+{
+    constexpr size_t respPayloadLength = 1;
+    constexpr std::array<uint8_t, sizeof(pldm_msg_hdr) + respPayloadLength>
+        getFirmwareParametersResp{0x00, 0x00, 0x00, 0x01};
+    auto responseMsg =
+        reinterpret_cast<const pldm_msg*>(getFirmwareParametersResp.data());
+    inventoryManager.getFirmwareParameters(1, responseMsg, respPayloadLength);
+    EXPECT_EQ(outComponentInfoMap.size(), 0);
+}
diff --git a/fw-update/test/meson.build b/fw-update/test/meson.build
new file mode 100644
index 0000000..e88466d
--- /dev/null
+++ b/fw-update/test/meson.build
@@ -0,0 +1,26 @@
+fw_update_test_src = declare_dependency(
+          sources: [
+            '../inventory_manager.cpp',
+            '../../common/utils.cpp',
+            '../../pldmd/dbus_impl_requester.cpp',
+            '../../pldmd/instance_id.cpp'])
+
+tests = [
+  'inventory_manager_test',
+]
+
+foreach t : tests
+  test(t, executable(t.underscorify(), t + '.cpp',
+                     implicit_include_directories: false,
+                     link_args: dynamic_linker,
+                     build_rpath: get_option('oe-sdk').enabled() ? rpath : '',
+                     dependencies: [
+                         fw_update_test_src,
+                         gtest,
+                         libpldm_dep,
+                         nlohmann_json,
+                         phosphor_dbus_interfaces,
+                         sdbusplus,
+                         sdeventplus]),
+       workdir: meson.current_source_dir())
+endforeach
diff --git a/meson.build b/meson.build
index 4757fe6..f63d950 100644
--- a/meson.build
+++ b/meson.build
@@ -194,6 +194,7 @@
   'pldmd/dbus_impl_requester.cpp',
   'pldmd/instance_id.cpp',
   'pldmd/dbus_impl_pdr.cpp',
+  'fw-update/inventory_manager.cpp',
   implicit_include_directories: false,
   dependencies: deps,
   install: true,
@@ -237,6 +238,7 @@
 
 if get_option('tests').enabled()
   subdir('common/test')
+  subdir('fw-update/test')
   subdir('host-bmc/test')
   subdir('requester/test')
   subdir('test')