pldmd: Wire fw-update-legacy into pldmd

The fw-update-legacy module provides the firmware update
functionality ported from the legacy pldmd-google daemon. This
change wires the legacy firmware manager into the
pldmd-google-dev-common main execution path. The manager now
inherits from MctpDiscoveryHandlerIntf and is registered with
the MctpDiscovery handler. Relevant PLDM_FWUP messages are
routed to it in the processRxMsg function, restoring the
firmware update features.

Upstream firmware update implementation requires a newer dbus
API version. Updating gBMC to use the newer version is a lot
much bigger effort as it is one of the fundamental dependencies
influencing the whole system, so the quickest way to achieve
feature parity between pldmd-google and
pldmd-google-dev-common was doing this instead.

Tested:
Compiled and verified on platform31. The pldmd daemon
successfully parses the endpoints and initiates
GetFirmwareParameters requests, closely matching the behavior
from the legacy pldmd-google logs. No firmware update
was attempted just yet.

Change-Id: I82044602e9519207fa7e0d8f41947b676bd4f7ab
Google-Bug-Id: 458166668
Signed-off-by: Luka Strizic <lstrz@google.com>
diff --git a/fw-update-legacy/activation.hpp b/fw-update-legacy/activation.hpp
new file mode 100644
index 0000000..3bda162
--- /dev/null
+++ b/fw-update-legacy/activation.hpp
@@ -0,0 +1,151 @@
+#pragma once
+
+#include "fw-update-legacy/update_manager.hpp"
+
+#include <sdbusplus/bus.hpp>
+#include <xyz/openbmc_project/Object/Delete/server.hpp>
+#include <xyz/openbmc_project/Software/Activation/server.hpp>
+#include <xyz/openbmc_project/Software/ActivationProgress/server.hpp>
+
+#include <string>
+
+namespace pldm
+{
+
+namespace fw_update
+{
+
+using ActivationIntf = sdbusplus::server::object_t<
+    sdbusplus::xyz::openbmc_project::Software::server::Activation>;
+using ActivationProgressIntf = sdbusplus::server::object_t<
+    sdbusplus::xyz::openbmc_project::Software::server::ActivationProgress>;
+using DeleteIntf = sdbusplus::server::object_t<
+    sdbusplus::xyz::openbmc_project::Object::server::Delete>;
+
+/** @class ActivationProgress
+ *
+ *  Concrete implementation of xyz.openbmc_project.Software.ActivationProgress
+ *  D-Bus interface
+ */
+class ActivationProgress : public ActivationProgressIntf
+{
+  public:
+    /** @brief Constructor
+     *
+     * @param[in] bus - Bus to attach to
+     * @param[in] objPath - D-Bus object path
+     */
+    ActivationProgress(sdbusplus::bus_t& bus, const std::string& objPath) :
+        ActivationProgressIntf(bus, objPath.c_str(),
+                               action::emit_interface_added)
+    {
+        progress(0);
+    }
+};
+
+/** @class Delete
+ *
+ *  Concrete implementation of xyz.openbmc_project.Object.Delete D-Bus interface
+ */
+class Delete : public DeleteIntf
+{
+  public:
+    /** @brief Constructor
+     *
+     *  @param[in] bus - Bus to attach to
+     *  @param[in] objPath - D-Bus object path
+     *  @param[in] updateManager - Reference to FW update manager
+     */
+    Delete(sdbusplus::bus_t& bus, const std::string& objPath,
+           UpdateManager* updateManager) :
+        DeleteIntf(bus, objPath.c_str(), action::emit_interface_added),
+        updateManager(updateManager)
+    {}
+
+    /** @brief Delete the Activation D-Bus object for the FW update package */
+    void delete_() override
+    {
+        updateManager->clearActivationInfo();
+    }
+
+  private:
+    UpdateManager* updateManager;
+};
+
+/** @class Activation
+ *
+ *  Concrete implementation of xyz.openbmc_project.Object.Activation D-Bus
+ *  interface
+ */
+class Activation : public ActivationIntf
+{
+  public:
+    /** @brief Constructor
+     *
+     *  @param[in] bus - Bus to attach to
+     *  @param[in] objPath - D-Bus object path
+     *  @param[in] updateManager - Reference to FW update manager
+     */
+    Activation(sdbusplus::bus_t& bus, std::string objPath,
+               Activations activationStatus, UpdateManager* updateManager) :
+        ActivationIntf(bus, objPath.c_str(),
+                       ActivationIntf::action::defer_emit),
+        bus(bus), objPath(objPath), updateManager(updateManager)
+    {
+        activation(activationStatus);
+        deleteImpl = std::make_unique<Delete>(bus, objPath, updateManager);
+        emit_object_added();
+    }
+
+    using sdbusplus::xyz::openbmc_project::Software::server::Activation::
+        activation;
+    using sdbusplus::xyz::openbmc_project::Software::server::Activation::
+        requestedActivation;
+
+    /** @brief Overriding Activation property setter function
+     */
+    Activations activation(Activations value) override
+    {
+        if (value == Activations::Activating)
+        {
+            deleteImpl.reset();
+            updateManager->activatePackage();
+        }
+        else if (value == Activations::Active || value == Activations::Failed)
+        {
+            if (!deleteImpl)
+            {
+                deleteImpl =
+                    std::make_unique<Delete>(bus, objPath, updateManager);
+            }
+        }
+
+        return ActivationIntf::activation(value);
+    }
+
+    /** @brief Overriding RequestedActivations property setter function
+     */
+    RequestedActivations requestedActivation(
+        RequestedActivations value) override
+    {
+        if ((value == RequestedActivations::Active) &&
+            (requestedActivation() != RequestedActivations::Active))
+        {
+            if ((ActivationIntf::activation() == Activations::Ready))
+            {
+                activation(Activations::Activating);
+            }
+        }
+        return ActivationIntf::requestedActivation(value);
+    }
+
+  private:
+    sdbusplus::bus_t& bus;
+    const std::string objPath;
+    UpdateManager* updateManager;
+    std::unique_ptr<Delete> deleteImpl;
+};
+
+} // namespace fw_update
+
+} // namespace pldm
diff --git a/fw-update-legacy/device_updater.cpp b/fw-update-legacy/device_updater.cpp
new file mode 100644
index 0000000..5c6ff0f
--- /dev/null
+++ b/fw-update-legacy/device_updater.cpp
@@ -0,0 +1,785 @@
+#include "device_updater.hpp"
+
+#include "activation.hpp"
+#include "update_manager.hpp"
+
+#include <libpldm/firmware_update.h>
+
+#include <phosphor-logging/lg2.hpp>
+
+#include <functional>
+
+PHOSPHOR_LOG2_USING;
+
+namespace pldm
+{
+
+namespace fw_update
+{
+
+void DeviceUpdater::startFwUpdateFlow()
+{
+    auto instanceIdResult = updateManager->instanceIdDb.next(eid);
+    if (!instanceIdResult)
+    {
+        error("Instance ID allocation failed for EID {EID}", "EID",
+              unsigned(eid));
+        throw pldm::InstanceIdError(instanceIdResult.error());
+    }
+    auto instanceId = instanceIdResult.value();
+    // NumberOfComponents
+    const auto& applicableComponents =
+        std::get<ApplicableComponents>(fwDeviceIDRecord);
+    // PackageDataLength
+    const auto& fwDevicePkgData =
+        std::get<FirmwareDevicePackageData>(fwDeviceIDRecord);
+    // ComponentImageSetVersionString
+    const auto& compImageSetVersion =
+        std::get<ComponentImageSetVersion>(fwDeviceIDRecord);
+    variable_field compImgSetVerStrInfo{};
+    compImgSetVerStrInfo.ptr =
+        reinterpret_cast<const uint8_t*>(compImageSetVersion.data());
+    compImgSetVerStrInfo.length =
+        static_cast<uint8_t>(compImageSetVersion.size());
+
+    Request request(
+        sizeof(pldm_msg_hdr) + sizeof(struct pldm_request_update_req) +
+        compImgSetVerStrInfo.length);
+    auto requestMsg = reinterpret_cast<pldm_msg*>(request.data());
+
+    auto rc = encode_request_update_req(
+        instanceId, maxTransferSize, applicableComponents.size(),
+        PLDM_FWUP_MIN_OUTSTANDING_REQ, fwDevicePkgData.size(),
+        PLDM_STR_TYPE_ASCII, compImgSetVerStrInfo.length, &compImgSetVerStrInfo,
+        requestMsg,
+        sizeof(struct pldm_request_update_req) + compImgSetVerStrInfo.length);
+    if (rc)
+    {
+        updateManager->instanceIdDb.free(eid, instanceId);
+        error("encode_request_update_req failed, EID = {EID}, RC = {RC}", "EID",
+              unsigned(eid), "RC", rc);
+        updateManager->updateDeviceCompletion(eid, false);
+        return;
+    }
+
+    rc = updateManager->handler.registerRequest(
+        eid, instanceId, PLDM_FWUP, PLDM_REQUEST_UPDATE, std::move(request),
+        std::move(std::bind_front(&DeviceUpdater::requestUpdate, this)));
+    if (rc)
+    {
+        error("Failed to send RequestUpdate request, EID = {EID}, RC = {RC}",
+              "EID", unsigned(eid), "RC", rc);
+        updateManager->updateDeviceCompletion(eid, false);
+        return;
+    }
+    else
+    {
+        info("EID {EID}: Sent RequestUpdate to EID {EID}.", "EID",
+             unsigned(eid));
+    }
+}
+
+void DeviceUpdater::requestUpdate(mctp_eid_t eid, const pldm_msg* response,
+                                  size_t respMsgLen)
+{
+    if (response == nullptr || !respMsgLen)
+    {
+        error("No response received for RequestUpdate, EID = {EID}", "EID",
+              unsigned(eid));
+        updateManager->updateDeviceCompletion(eid, false);
+        return;
+    }
+
+    uint8_t completionCode = 0;
+    uint16_t fdMetaDataLen = 0;
+    uint8_t fdWillSendPkgData = 0;
+
+    auto rc = decode_request_update_resp(response, respMsgLen, &completionCode,
+                                         &fdMetaDataLen, &fdWillSendPkgData);
+    if (rc)
+    {
+        error("Decoding RequestUpdate response failed, EID = {EID}, RC = {RC}",
+              "EID", unsigned(eid), "RC", rc);
+        updateManager->updateDeviceCompletion(eid, false);
+        return;
+    }
+    if (completionCode)
+    {
+        error(
+            "RequestUpdate response failed with error completion code, EID = {EID}, CC = {CC}",
+            "EID", unsigned(eid), "CC", unsigned(completionCode));
+        updateManager->updateDeviceCompletion(eid, false);
+        return;
+    }
+    else
+    {
+        info("EID {EID}: RequestUpdate succeeded.", "EID", unsigned(eid));
+    }
+
+    // Optional fields DeviceMetaData and GetPackageData not handled
+    pldmRequest = std::make_unique<sdeventplus::source::Defer>(
+        updateManager->event,
+        std::bind(&DeviceUpdater::sendPassCompTableRequest, this,
+                  componentIndex));
+}
+
+void DeviceUpdater::sendPassCompTableRequest(size_t offset)
+{
+    pldmRequest.reset();
+
+    auto instanceIdResult = updateManager->instanceIdDb.next(eid);
+    if (!instanceIdResult)
+    {
+        error("Instance ID allocation failed for EID {EID}", "EID",
+              unsigned(eid));
+        throw pldm::InstanceIdError(instanceIdResult.error());
+    }
+    auto instanceId = instanceIdResult.value();
+    // TransferFlag
+    const auto& applicableComponents =
+        std::get<ApplicableComponents>(fwDeviceIDRecord);
+    uint8_t transferFlag = 0;
+    if (applicableComponents.size() == 1)
+    {
+        transferFlag = PLDM_START_AND_END;
+    }
+    else if (offset == 0)
+    {
+        transferFlag = PLDM_START;
+    }
+    else if (offset == applicableComponents.size() - 1)
+    {
+        transferFlag = PLDM_END;
+    }
+    else
+    {
+        transferFlag = PLDM_MIDDLE;
+    }
+    const auto& comp = compImageInfos[applicableComponents[offset]];
+    // ComponentClassification
+    CompClassification compClassification = std::get<static_cast<size_t>(
+        ComponentImageInfoPos::CompClassificationPos)>(comp);
+    // ComponentIdentifier
+    CompIdentifier compIdentifier =
+        std::get<static_cast<size_t>(ComponentImageInfoPos::CompIdentifierPos)>(
+            comp);
+    // ComponentClassificationIndex
+    CompClassificationIndex compClassificationIndex{};
+    auto compKey = std::make_pair(compClassification, compIdentifier);
+    if (compInfo.contains(compKey))
+    {
+        auto search = compInfo.find(compKey);
+        compClassificationIndex = search->second;
+    }
+    else
+    {
+        error(
+            "Component not found in Component Info, EID = {EID}, COMP_CLASS = {COMP_CLS}, COMP_ID = {COMP_ID}",
+            "EID", unsigned(eid), "COMP_CLS", compClassification, "COMP_ID",
+            compIdentifier);
+        updateManager->updateDeviceCompletion(eid, false);
+        return;
+    }
+    // ComponentComparisonStamp
+    CompComparisonStamp compComparisonStamp = std::get<static_cast<size_t>(
+        ComponentImageInfoPos::CompComparisonStampPos)>(comp);
+    // ComponentVersionString
+    const auto& compVersion =
+        std::get<static_cast<size_t>(ComponentImageInfoPos::CompVersionPos)>(
+            comp);
+    variable_field compVerStrInfo{};
+    compVerStrInfo.ptr = reinterpret_cast<const uint8_t*>(compVersion.data());
+    compVerStrInfo.length = static_cast<uint8_t>(compVersion.size());
+
+    Request request(
+        sizeof(pldm_msg_hdr) + sizeof(struct pldm_pass_component_table_req) +
+        compVerStrInfo.length);
+    auto requestMsg = reinterpret_cast<pldm_msg*>(request.data());
+    auto rc = encode_pass_component_table_req(
+        instanceId, transferFlag, compClassification, compIdentifier,
+        compClassificationIndex, compComparisonStamp, PLDM_STR_TYPE_ASCII,
+        compVerStrInfo.length, &compVerStrInfo, requestMsg,
+        sizeof(pldm_pass_component_table_req) + compVerStrInfo.length);
+    if (rc)
+    {
+        updateManager->instanceIdDb.free(eid, instanceId);
+        error("encode_pass_component_table_req failed, EID = {EID}, RC = {RC}",
+              "EID", unsigned(eid), "RC", rc);
+        updateManager->updateDeviceCompletion(eid, false);
+        return;
+    }
+
+    rc = updateManager->handler.registerRequest(
+        eid, instanceId, PLDM_FWUP, PLDM_PASS_COMPONENT_TABLE,
+        std::move(request),
+        std::move(std::bind_front(&DeviceUpdater::passCompTable, this)));
+    if (rc)
+    {
+        error(
+            "Failed to send PassComponentTable request, EID = {EID}, RC = {RC}",
+            "EID", unsigned(eid), "RC", rc);
+        updateManager->updateDeviceCompletion(eid, false);
+        return;
+    }
+    else
+    {
+        info("EID {EID}: Sent PassComponentTable to EID {EID}.", "EID",
+             unsigned(eid));
+    }
+}
+
+void DeviceUpdater::passCompTable(mctp_eid_t eid, const pldm_msg* response,
+                                  size_t respMsgLen)
+{
+    if (response == nullptr || !respMsgLen)
+    {
+        error("No response received for PassComponentTable, EID = {EID}", "EID",
+              unsigned(eid));
+        updateManager->updateDeviceCompletion(eid, false);
+        return;
+    }
+
+    uint8_t completionCode = 0;
+    uint8_t compResponse = 0;
+    uint8_t compResponseCode = 0;
+
+    auto rc =
+        decode_pass_component_table_resp(response, respMsgLen, &completionCode,
+                                         &compResponse, &compResponseCode);
+    if (rc)
+    {
+        error(
+            "Decoding PassComponentTable response failed, EID={EID}, RC = {RC}",
+            "EID", unsigned(eid), "RC", rc);
+        updateManager->updateDeviceCompletion(eid, false);
+        return;
+    }
+    if (completionCode)
+    {
+        error(
+            "PassComponentTable response failed with error completion code, EID = {EID}, CC = {CC}",
+            "EID", unsigned(eid), "CC", unsigned(completionCode));
+        updateManager->updateDeviceCompletion(eid, false);
+        return;
+    }
+    else
+    {
+        info("EID {EID}: PassComponentTable succeeded.", "EID", unsigned(eid));
+    }
+    // Handle ComponentResponseCode
+
+    const auto& applicableComponents =
+        std::get<ApplicableComponents>(fwDeviceIDRecord);
+    if (componentIndex == applicableComponents.size() - 1)
+    {
+        componentIndex = 0;
+        pldmRequest = std::make_unique<sdeventplus::source::Defer>(
+            updateManager->event,
+            std::bind(&DeviceUpdater::sendUpdateComponentRequest, this,
+                      componentIndex));
+    }
+    else
+    {
+        componentIndex++;
+        pldmRequest = std::make_unique<sdeventplus::source::Defer>(
+            updateManager->event,
+            std::bind(&DeviceUpdater::sendPassCompTableRequest, this,
+                      componentIndex));
+    }
+}
+
+void DeviceUpdater::sendUpdateComponentRequest(size_t offset)
+{
+    pldmRequest.reset();
+
+    auto instanceIdResult = updateManager->instanceIdDb.next(eid);
+    if (!instanceIdResult)
+    {
+        error("Instance ID allocation failed for EID {EID}", "EID",
+              unsigned(eid));
+        throw pldm::InstanceIdError(instanceIdResult.error());
+    }
+    auto instanceId = instanceIdResult.value();
+    const auto& applicableComponents =
+        std::get<ApplicableComponents>(fwDeviceIDRecord);
+    const auto& comp = compImageInfos[applicableComponents[offset]];
+    // ComponentClassification
+    CompClassification compClassification = std::get<static_cast<size_t>(
+        ComponentImageInfoPos::CompClassificationPos)>(comp);
+    // ComponentIdentifier
+    CompIdentifier compIdentifier =
+        std::get<static_cast<size_t>(ComponentImageInfoPos::CompIdentifierPos)>(
+            comp);
+    // ComponentClassificationIndex
+    CompClassificationIndex compClassificationIndex{};
+    auto compKey = std::make_pair(compClassification, compIdentifier);
+    if (compInfo.contains(compKey))
+    {
+        auto search = compInfo.find(compKey);
+        compClassificationIndex = search->second;
+    }
+    else
+    {
+        error(
+            "Component not found in Component Info, EID = {EID}, COMP_CLASS = {COMP_CLS}, COMP_ID = {COMP_ID}",
+            "EID", unsigned(eid), "COMP_CLS", compClassification, "COMP_ID",
+            compIdentifier);
+        updateManager->updateDeviceCompletion(eid, false);
+        return;
+    }
+
+    // UpdateOptionFlags
+    bitfield32_t updateOptionFlags;
+    updateOptionFlags.bits.bit0 = std::get<3>(comp)[0];
+    // ComponentVersion
+    const auto& compVersion = std::get<7>(comp);
+    variable_field compVerStrInfo{};
+    compVerStrInfo.ptr = reinterpret_cast<const uint8_t*>(compVersion.data());
+    compVerStrInfo.length = static_cast<uint8_t>(compVersion.size());
+
+    Request request(
+        sizeof(pldm_msg_hdr) + sizeof(struct pldm_update_component_req) +
+        compVerStrInfo.length);
+    auto requestMsg = reinterpret_cast<pldm_msg*>(request.data());
+
+    auto rc = encode_update_component_req(
+        instanceId, compClassification, compIdentifier, compClassificationIndex,
+        std::get<static_cast<size_t>(
+            ComponentImageInfoPos::CompComparisonStampPos)>(comp),
+        std::get<static_cast<size_t>(ComponentImageInfoPos::CompSizePos)>(comp),
+        updateOptionFlags, PLDM_STR_TYPE_ASCII, compVerStrInfo.length,
+        &compVerStrInfo, requestMsg,
+        sizeof(pldm_update_component_req) + compVerStrInfo.length);
+    if (rc)
+    {
+        updateManager->instanceIdDb.free(eid, instanceId);
+        error("encode_update_component_req failed, EID={EID}, RC = {RC}", "EID",
+              unsigned(eid), "RC", rc);
+        updateManager->updateDeviceCompletion(eid, false);
+        return;
+    }
+
+    rc = updateManager->handler.registerRequest(
+        eid, instanceId, PLDM_FWUP, PLDM_UPDATE_COMPONENT, std::move(request),
+        std::move(std::bind_front(&DeviceUpdater::updateComponent, this)));
+    if (rc)
+    {
+        error("Failed to send UpdateComponent request, EID={EID}, RC = {RC}",
+              "EID", unsigned(eid), "RC", rc);
+        updateManager->updateDeviceCompletion(eid, false);
+        return;
+    }
+    else
+    {
+        info("EID {EID}: Sent UpdateComponent to EID {EID}.", "EID",
+             unsigned(eid));
+    }
+}
+
+void DeviceUpdater::updateComponent(mctp_eid_t eid, const pldm_msg* response,
+                                    size_t respMsgLen)
+{
+    if (response == nullptr || !respMsgLen)
+    {
+        error("No response received for updateComponent, EID={EID}", "EID",
+              unsigned(eid));
+        updateManager->updateDeviceCompletion(eid, false);
+        return;
+    }
+
+    uint8_t completionCode = 0;
+    uint8_t compCompatibilityResp = 0;
+    uint8_t compCompatibilityRespCode = 0;
+    bitfield32_t updateOptionFlagsEnabled{};
+    uint16_t timeBeforeReqFWData = 0;
+
+    auto rc = decode_update_component_resp(
+        response, respMsgLen, &completionCode, &compCompatibilityResp,
+        &compCompatibilityRespCode, &updateOptionFlagsEnabled,
+        &timeBeforeReqFWData);
+    if (rc)
+    {
+        error("Decoding UpdateComponent response failed, EID={EID}, RC = {RC}",
+              "EID", unsigned(eid), "RC", rc);
+        updateManager->updateDeviceCompletion(eid, false);
+        return;
+    }
+    if (completionCode)
+    {
+        error(
+            "UpdateComponent response failed with error completion code, EID = {EID}, CC = {CC}",
+            "EID", unsigned(eid), "CC", unsigned(completionCode));
+        updateManager->updateDeviceCompletion(eid, false);
+        return;
+    }
+
+    info(
+        "EID {EID}: Got successful response for UpdateComponent, device will start to RequestFirmwareData...",
+        "EID", unsigned(eid));
+}
+
+Response DeviceUpdater::requestFwData(const pldm_msg* request,
+                                      size_t payloadLength)
+{
+    uint8_t completionCode = PLDM_SUCCESS;
+    uint32_t offset = 0;
+    uint32_t length = 0;
+    Response response(sizeof(pldm_msg_hdr) + sizeof(completionCode), 0);
+    auto responseMsg = reinterpret_cast<pldm_msg*>(response.data());
+    auto rc = decode_request_firmware_data_req(request, payloadLength, &offset,
+                                               &length);
+    if (rc)
+    {
+        error(
+            "Decoding RequestFirmwareData request failed, EID={EID}, RC = {RC}",
+            "EID", unsigned(eid), "RC", rc);
+        rc = encode_request_firmware_data_resp(
+            request->hdr.instance_id, PLDM_ERROR_INVALID_DATA, responseMsg,
+            sizeof(completionCode));
+        if (rc)
+        {
+            error(
+                "Encoding RequestFirmwareData response failed, EID = {EID}, RC = {RC}",
+                "EID", unsigned(eid), "RC", rc);
+        }
+        return response;
+    }
+
+    const auto& applicableComponents =
+        std::get<ApplicableComponents>(fwDeviceIDRecord);
+    const auto& comp = compImageInfos[applicableComponents[componentIndex]];
+    auto compOffset = std::get<5>(comp);
+    auto compSize = std::get<6>(comp);
+
+    // Print out progress around 10 times to prevent log spam
+    uint32_t chunk = (compSize >= 10) ? compSize / 10 : 1;
+    uint32_t boundary = offset / chunk * chunk;
+    if ((offset < boundary + length) && (offset >= boundary))
+    {
+        info(
+            "EID {EID}: offset = {OFFSET}, length = {LEN}, boundary = {BOUNDARY}",
+            "EID", unsigned(eid), "OFFSET", unsigned(offset), "LEN",
+            unsigned(length), "BOUNDARY", boundary);
+    }
+
+    if (length < PLDM_FWUP_BASELINE_TRANSFER_SIZE || length > maxTransferSize)
+    {
+        rc = encode_request_firmware_data_resp(
+            request->hdr.instance_id, PLDM_FWUP_INVALID_TRANSFER_LENGTH,
+            responseMsg, sizeof(completionCode));
+        if (rc)
+        {
+            error(
+                "Encoding RequestFirmwareData response failed, EID={EID}, RC = {RC}",
+                "EID", unsigned(eid), "RC", rc);
+        }
+        return response;
+    }
+
+    if (offset + length > compSize + PLDM_FWUP_BASELINE_TRANSFER_SIZE)
+    {
+        rc = encode_request_firmware_data_resp(
+            request->hdr.instance_id, PLDM_FWUP_DATA_OUT_OF_RANGE, responseMsg,
+            sizeof(completionCode));
+        if (rc)
+        {
+            error(
+                "Encoding RequestFirmwareData response failed, EID={EID}, RC = {RC}",
+                "EID", unsigned(eid), "RC", rc);
+        }
+        return response;
+    }
+
+    size_t padBytes = 0;
+    if (offset + length > compSize)
+    {
+        padBytes = offset + length - compSize;
+    }
+
+    response.resize(sizeof(pldm_msg_hdr) + sizeof(completionCode) + length);
+    responseMsg = reinterpret_cast<pldm_msg*>(response.data());
+    package.seekg(compOffset + offset);
+    package.read(
+        reinterpret_cast<char*>(
+            response.data() + sizeof(pldm_msg_hdr) + sizeof(completionCode)),
+        length - padBytes);
+    rc = encode_request_firmware_data_resp(
+        request->hdr.instance_id, completionCode, responseMsg,
+        sizeof(completionCode));
+    if (rc)
+    {
+        error(
+            "Encoding RequestFirmwareData response failed, EID={EID}, RC = {RC}",
+            "EID", unsigned(eid), "RC", rc);
+        return response;
+    }
+
+    return response;
+}
+
+Response DeviceUpdater::transferComplete(const pldm_msg* request,
+                                         size_t payloadLength)
+{
+    uint8_t completionCode = PLDM_SUCCESS;
+    Response response(sizeof(pldm_msg_hdr) + sizeof(completionCode), 0);
+    auto responseMsg = reinterpret_cast<pldm_msg*>(response.data());
+
+    uint8_t transferResult = 0;
+    auto rc =
+        decode_transfer_complete_req(request, payloadLength, &transferResult);
+    if (rc)
+    {
+        error("Decoding TransferComplete request failed, EID={EID}, RC = {RC}",
+              "EID", unsigned(eid), "RC", rc);
+        rc = encode_transfer_complete_resp(request->hdr.instance_id,
+                                           PLDM_ERROR_INVALID_DATA, responseMsg,
+                                           sizeof(completionCode));
+        if (rc)
+        {
+            error(
+                "Encoding TransferComplete response failed, EID={EID}, RC = {RC}",
+                "EID", unsigned(eid), "RC", rc);
+        }
+        return response;
+    }
+
+    const auto& applicableComponents =
+        std::get<ApplicableComponents>(fwDeviceIDRecord);
+    const auto& comp = compImageInfos[applicableComponents[componentIndex]];
+    const auto& compVersion = std::get<7>(comp);
+
+    if (transferResult == PLDM_FWUP_TRANSFER_SUCCESS)
+    {
+        info(
+            "Component Transfer complete, EID = {EID}, COMPONENT_VERSION = {COMP_VERS}",
+            "EID", unsigned(eid), "COMP_VERS", compVersion);
+    }
+    else
+    {
+        error(
+            "Transfer of the component failed, EID={EID}, COMPONENT_VERSION = {COMP_VERS}, TRANSFER_RESULT = {TRANS_RES}",
+            "EID", unsigned(eid), "COMP_VERS", compVersion, "TRANS_RES",
+            unsigned(transferResult));
+    }
+
+    rc = encode_transfer_complete_resp(request->hdr.instance_id, completionCode,
+                                       responseMsg, sizeof(completionCode));
+    if (rc)
+    {
+        error("Encoding TransferComplete response failed, EID={EID}, RC = {RC}",
+              "EID", unsigned(eid), "RC", rc);
+        return response;
+    }
+
+    return response;
+}
+
+Response DeviceUpdater::verifyComplete(const pldm_msg* request,
+                                       size_t payloadLength)
+{
+    uint8_t completionCode = PLDM_SUCCESS;
+    Response response(sizeof(pldm_msg_hdr) + sizeof(completionCode), 0);
+    auto responseMsg = reinterpret_cast<pldm_msg*>(response.data());
+
+    uint8_t verifyResult = 0;
+    auto rc = decode_verify_complete_req(request, payloadLength, &verifyResult);
+    if (rc)
+    {
+        error("Decoding VerifyComplete request failed, EID = {EID}, RC = {RC}",
+              "EID", unsigned(eid), "RC", rc);
+        rc = encode_verify_complete_resp(request->hdr.instance_id,
+                                         PLDM_ERROR_INVALID_DATA, responseMsg,
+                                         sizeof(completionCode));
+        if (rc)
+        {
+            error(
+                "Encoding VerifyComplete response failed, EID={EID}, RC = {RC}",
+                "EID", unsigned(eid), "RC", rc);
+        }
+        return response;
+    }
+
+    const auto& applicableComponents =
+        std::get<ApplicableComponents>(fwDeviceIDRecord);
+    const auto& comp = compImageInfos[applicableComponents[componentIndex]];
+    const auto& compVersion = std::get<7>(comp);
+
+    if (verifyResult == PLDM_FWUP_VERIFY_SUCCESS)
+    {
+        info(
+            "Component verification complete, EID={EID}, COMPONENT_VERSION={COMP_VERS}",
+            "EID", unsigned(eid), "COMP_VERS", compVersion);
+    }
+    else
+    {
+        error(
+            "Component verification failed, EID={EID}, COMPONENT_VERSION={COMP_VERS}, VERIFY_RESULT={VERIFY_RES}",
+            "EID", unsigned(eid), "COMP_VERS", compVersion, "VERIFY_RES",
+            unsigned(verifyResult));
+    }
+
+    rc = encode_verify_complete_resp(request->hdr.instance_id, completionCode,
+                                     responseMsg, sizeof(completionCode));
+    if (rc)
+    {
+        error("Encoding VerifyComplete response failed, EID={EID}, RC = {RC}",
+              "EID", unsigned(eid), "RC", rc);
+        return response;
+    }
+
+    return response;
+}
+
+Response DeviceUpdater::applyComplete(const pldm_msg* request,
+                                      size_t payloadLength)
+{
+    uint8_t completionCode = PLDM_SUCCESS;
+    Response response(sizeof(pldm_msg_hdr) + sizeof(completionCode), 0);
+    auto responseMsg = reinterpret_cast<pldm_msg*>(response.data());
+
+    uint8_t applyResult = 0;
+    bitfield16_t compActivationModification{};
+    auto rc = decode_apply_complete_req(request, payloadLength, &applyResult,
+                                        &compActivationModification);
+    if (rc)
+    {
+        error("Decoding ApplyComplete request failed, EID={EID}, RC = {RC}",
+              "EID", unsigned(eid), "RC", rc);
+        rc = encode_apply_complete_resp(request->hdr.instance_id,
+                                        PLDM_ERROR_INVALID_DATA, responseMsg,
+                                        sizeof(completionCode));
+        if (rc)
+        {
+            error(
+                "Encoding ApplyComplete response failed, EID={EID}, RC = {RC}",
+                "EID", unsigned(eid), "RC", rc);
+        }
+        return response;
+    }
+
+    const auto& applicableComponents =
+        std::get<ApplicableComponents>(fwDeviceIDRecord);
+    const auto& comp = compImageInfos[applicableComponents[componentIndex]];
+    const auto& compVersion = std::get<7>(comp);
+
+    if (applyResult == PLDM_FWUP_APPLY_SUCCESS ||
+        applyResult == PLDM_FWUP_APPLY_SUCCESS_WITH_ACTIVATION_METHOD)
+    {
+        info(
+            "Component apply complete, EID = {EID}, COMPONENT_VERSION = {COMP_VERS}",
+            "EID", unsigned(eid), "COMP_VERS", compVersion);
+        updateManager->updateActivationProgress();
+    }
+    else
+    {
+        error(
+            "Component apply failed, EID = {EID}, COMPONENT_VERSION = {COMP_VERS}, APPLY_RESULT = {APPLY_RES}",
+            "EID", unsigned(eid), "COMP_VERS", compVersion, "APPLY_RES",
+            unsigned(applyResult));
+    }
+
+    rc = encode_apply_complete_resp(request->hdr.instance_id, completionCode,
+                                    responseMsg, sizeof(completionCode));
+    if (rc)
+    {
+        error("Encoding ApplyComplete response failed, EID={EID}, RC = {RC}",
+              "EID", unsigned(eid), "RC", rc);
+        return response;
+    }
+
+    if (componentIndex == applicableComponents.size() - 1)
+    {
+        componentIndex = 0;
+        pldmRequest = std::make_unique<sdeventplus::source::Defer>(
+            updateManager->event,
+            std::bind(&DeviceUpdater::sendActivateFirmwareRequest, this));
+    }
+    else
+    {
+        componentIndex++;
+        pldmRequest = std::make_unique<sdeventplus::source::Defer>(
+            updateManager->event,
+            std::bind(&DeviceUpdater::sendUpdateComponentRequest, this,
+                      componentIndex));
+    }
+
+    return response;
+}
+
+void DeviceUpdater::sendActivateFirmwareRequest()
+{
+    pldmRequest.reset();
+    auto instanceIdResult = updateManager->instanceIdDb.next(eid);
+    if (!instanceIdResult)
+    {
+        error("Instance ID allocation failed for EID {EID}", "EID",
+              unsigned(eid));
+        throw pldm::InstanceIdError(instanceIdResult.error());
+    }
+    auto instanceId = instanceIdResult.value();
+    Request request(
+        sizeof(pldm_msg_hdr) + sizeof(struct pldm_activate_firmware_req));
+    auto requestMsg = reinterpret_cast<pldm_msg*>(request.data());
+
+    auto rc = encode_activate_firmware_req(
+        instanceId, PLDM_NOT_ACTIVATE_SELF_CONTAINED_COMPONENTS, requestMsg,
+        sizeof(pldm_activate_firmware_req));
+    if (rc)
+    {
+        updateManager->instanceIdDb.free(eid, instanceId);
+        error("encode_activate_firmware_req failed, EID={EID}, RC = {RC}",
+              "EID", unsigned(eid), "RC", rc);
+        updateManager->updateDeviceCompletion(eid, false);
+        return;
+    }
+
+    rc = updateManager->handler.registerRequest(
+        eid, instanceId, PLDM_FWUP, PLDM_ACTIVATE_FIRMWARE, std::move(request),
+        std::move(std::bind_front(&DeviceUpdater::activateFirmware, this)));
+    if (rc)
+    {
+        error("Failed to send ActivateFirmware request, EID={EID}, RC = {RC}",
+              "EID", unsigned(eid), "RC", rc);
+        updateManager->updateDeviceCompletion(eid, false);
+        return;
+    }
+}
+
+void DeviceUpdater::activateFirmware(mctp_eid_t eid, const pldm_msg* response,
+                                     size_t respMsgLen)
+{
+    if (response == nullptr || !respMsgLen)
+    {
+        error("No response received for ActivateFirmware, EID={EID}", "EID",
+              unsigned(eid));
+        updateManager->updateDeviceCompletion(eid, false);
+        return;
+    }
+
+    uint8_t completionCode = 0;
+    uint16_t estimatedTimeForActivation = 0;
+
+    auto rc = decode_activate_firmware_resp(
+        response, respMsgLen, &completionCode, &estimatedTimeForActivation);
+    if (rc)
+    {
+        error("Decoding ActivateFirmware response failed, EID={EID}, RC = {RC}",
+              "EID", unsigned(eid), "RC", rc);
+        updateManager->updateDeviceCompletion(eid, false);
+        return;
+    }
+    if (completionCode)
+    {
+        error(
+            "ActivateFirmware response failed with error completion code, EID = {EID}, CC = {CC}",
+            "EID", unsigned(eid), "CC", unsigned(completionCode));
+        updateManager->updateDeviceCompletion(eid, false);
+        return;
+    }
+
+    updateManager->updateDeviceCompletion(eid, true);
+}
+
+} // namespace fw_update
+
+} // namespace pldm
diff --git a/fw-update-legacy/device_updater.hpp b/fw-update-legacy/device_updater.hpp
new file mode 100644
index 0000000..4613a7c
--- /dev/null
+++ b/fw-update-legacy/device_updater.hpp
@@ -0,0 +1,214 @@
+#pragma once
+
+#include "common/types.hpp"
+#include "requester/handler.hpp"
+#include "requester/request.hpp"
+
+#include <sdeventplus/event.hpp>
+#include <sdeventplus/source/event.hpp>
+
+#include <fstream>
+
+namespace pldm
+{
+
+namespace fw_update
+{
+
+class UpdateManager;
+
+/** @class DeviceUpdater
+ *
+ *  DeviceUpdater orchestrates the firmware update of the firmware device and
+ *  updates the UpdateManager about the status once it is complete.
+ */
+class DeviceUpdater
+{
+  public:
+    DeviceUpdater() = delete;
+    DeviceUpdater(const DeviceUpdater&) = delete;
+    DeviceUpdater(DeviceUpdater&&) = default;
+    DeviceUpdater& operator=(const DeviceUpdater&) = delete;
+    DeviceUpdater& operator=(DeviceUpdater&&) = delete;
+    ~DeviceUpdater() = default;
+
+    /** @brief Constructor
+     *
+     *  @param[in] eid - Endpoint ID of the firmware device
+     *  @param[in] package - File stream for firmware update package
+     *  @param[in] fwDeviceIDRecord - FirmwareDeviceIDRecord in the fw update
+     *                                package that matches this firmware device
+     *  @param[in] compImageInfos - Component image information for all the
+     *                              components in the fw update package
+     *  @param[in] compInfo - Component info for the components in this FD
+     *                        derived from GetFirmwareParameters response
+     *  @param[in] maxTransferSize - Maximum size in bytes of the variable
+     *                               payload allowed to be requested by the FD
+     *  @param[in] updateManager - To update the status of fw update of the
+     *                             device
+     */
+    explicit DeviceUpdater(mctp_eid_t eid, std::ifstream& package,
+                           const FirmwareDeviceIDRecord& fwDeviceIDRecord,
+                           const ComponentImageInfos& compImageInfos,
+                           const ComponentInfo& compInfo,
+                           uint32_t maxTransferSize,
+                           UpdateManager* updateManager) :
+        eid(eid), package(package), fwDeviceIDRecord(fwDeviceIDRecord),
+        compImageInfos(compImageInfos), compInfo(compInfo),
+        maxTransferSize(maxTransferSize), updateManager(updateManager)
+    {}
+
+    /** @brief Start the firmware update flow for the FD
+     *
+     *  To start the update flow RequestUpdate command is sent to the FD.
+     *
+     */
+    void startFwUpdateFlow();
+
+    /** @brief Handler for RequestUpdate command response
+     *
+     *  The response of the RequestUpdate is processed and if the response
+     *  is success, send PassComponentTable request to FD.
+     *
+     *  @param[in] eid - Remote MCTP endpoint
+     *  @param[in] response - PLDM response message
+     *  @param[in] respMsgLen - Response message length
+     */
+    void requestUpdate(mctp_eid_t eid, const pldm_msg* response,
+                       size_t respMsgLen);
+
+    /** @brief Handler for PassComponentTable command response
+     *
+     *  The response of the PassComponentTable is processed. If the response
+     *  indicates component can be updated, continue with either a) or b).
+     *
+     *  a. Send PassComponentTable request for the next component if
+     *     applicable
+     *  b. UpdateComponent command to request updating a specific
+     *     firmware component
+     *
+     *  If the response indicates component may be updateable, continue
+     *  based on the policy in DeviceUpdateOptionFlags.
+     *
+     *  @param[in] eid - Remote MCTP endpoint
+     *  @param[in] response - PLDM response message
+     *  @param[in] respMsgLen - Response message length
+     */
+    void passCompTable(mctp_eid_t eid, const pldm_msg* response,
+                       size_t respMsgLen);
+
+    /** @brief Handler for UpdateComponent command response
+     *
+     *  The response of the UpdateComponent is processed and will wait for
+     *  FD to request the firmware data.
+     *
+     *  @param[in] eid - Remote MCTP endpoint
+     *  @param[in] response - PLDM response message
+     *  @param[in] respMsgLen - Response message length
+     */
+    void updateComponent(mctp_eid_t eid, const pldm_msg* response,
+                         size_t respMsgLen);
+
+    /** @brief Handler for RequestFirmwareData request
+     *
+     *  @param[in] request - Request message
+     *  @param[in] payload_length - Request message payload length
+     *  @return Response - PLDM Response message
+     */
+    Response requestFwData(const pldm_msg* request, size_t payloadLength);
+
+    /** @brief Handler for TransferComplete request
+     *
+     *  @param[in] request - Request message
+     *  @param[in] payload_length - Request message payload length
+     *  @return Response - PLDM Response message
+     */
+    Response transferComplete(const pldm_msg* request, size_t payloadLength);
+
+    /** @brief Handler for VerifyComplete request
+     *
+     *  @param[in] request - Request message
+     *  @param[in] payload_length - Request message payload length
+     *  @return Response - PLDM Response message
+     */
+    Response verifyComplete(const pldm_msg* request, size_t payloadLength);
+
+    /** @brief Handler for ApplyComplete request
+     *
+     *  @param[in] request - Request message
+     *  @param[in] payload_length - Request message payload length
+     *  @return Response - PLDM Response message
+     */
+    Response applyComplete(const pldm_msg* request, size_t payloadLength);
+
+    /** @brief Handler for ActivateFirmware command response
+     *
+     *  The response of the ActivateFirmware is processed and will update the
+     *  UpdateManager with the completion of the firmware update.
+     *
+     *  @param[in] eid - Remote MCTP endpoint
+     *  @param[in] response - PLDM response message
+     *  @param[in] respMsgLen - Response message length
+     */
+    void activateFirmware(mctp_eid_t eid, const pldm_msg* response,
+                          size_t respMsgLen);
+
+  private:
+    /** @brief Send PassComponentTable command request
+     *
+     *  @param[in] compOffset - component offset in compImageInfos
+     */
+    void sendPassCompTableRequest(size_t offset);
+
+    /** @brief Send UpdateComponent command request
+     *
+     *  @param[in] compOffset - component offset in compImageInfos
+     */
+    void sendUpdateComponentRequest(size_t offset);
+
+    /** @brief Send ActivateFirmware command request */
+    void sendActivateFirmwareRequest();
+
+    /** @brief Endpoint ID of the firmware device */
+    mctp_eid_t eid;
+
+    /** @brief File stream for firmware update package */
+    std::ifstream& package;
+
+    /** @brief FirmwareDeviceIDRecord in the fw update package that matches this
+     *         firmware device
+     */
+    const FirmwareDeviceIDRecord& fwDeviceIDRecord;
+
+    /** @brief Component image information for all the components in the fw
+     *         update package
+     */
+    const ComponentImageInfos& compImageInfos;
+
+    /** @brief Component info for the components in this FD derived from
+     *         GetFirmwareParameters response
+     */
+    const ComponentInfo& compInfo;
+
+    /** @brief Maximum size in bytes of the variable payload to be requested by
+     *         the FD via RequestFirmwareData command
+     */
+    uint32_t maxTransferSize;
+
+    /** @brief To update the status of fw update of the FD */
+    UpdateManager* updateManager;
+
+    /** @brief Component index is used to track the current component being
+     *         updated if multiple components are applicable for the FD.
+     *         It is also used to keep track of the next component in
+     *         PassComponentTable
+     */
+    size_t componentIndex = 0;
+
+    /** @brief To send a PLDM request after the current command handling */
+    std::unique_ptr<sdeventplus::source::Defer> pldmRequest;
+};
+
+} // namespace fw_update
+
+} // namespace pldm
diff --git a/fw-update-legacy/inventory_manager.cpp b/fw-update-legacy/inventory_manager.cpp
new file mode 100644
index 0000000..207f512
--- /dev/null
+++ b/fw-update-legacy/inventory_manager.cpp
@@ -0,0 +1,340 @@
+#include "inventory_manager.hpp"
+
+#include "common/utils.hpp"
+#include "xyz/openbmc_project/Software/Version/server.hpp"
+
+#include <libpldm/firmware_update.h>
+
+#include <phosphor-logging/lg2.hpp>
+
+#include <functional>
+
+PHOSPHOR_LOG2_USING;
+
+namespace pldm
+{
+namespace fw_update
+{
+void InventoryManager::discoverFDs(const std::vector<mctp_eid_t>& eids,
+                                   const int retriesLeft)
+{
+    for (const auto& eid : eids)
+    {
+        auto instanceIdResult = instanceIdDb.next(eid);
+        if (!instanceIdResult)
+        {
+            error("Instance ID allocation failed for EID {EID}", "EID",
+                  unsigned(eid));
+            throw pldm::InstanceIdError(instanceIdResult.error());
+        }
+        auto instanceId = instanceIdResult.value();
+        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)
+        {
+            instanceIdDb.free(eid, instanceId);
+            error(
+                "encode_query_device_identifiers_req failed, EID={EID}, RC = {RC}",
+                "EID", unsigned(eid), "RC", rc);
+            continue;
+        }
+
+        rc = handler.registerRequest(
+            eid, instanceId, PLDM_FWUP, PLDM_QUERY_DEVICE_IDENTIFIERS,
+            std::move(requestMsg),
+            std::move(std::bind_front(&InventoryManager::queryDeviceIdentifiers,
+                                      this, retriesLeft)));
+        if (rc)
+        {
+            error(
+                "Failed to send QueryDeviceIdentifiers request, EID={EID}, RC = {RC}",
+                "EID", unsigned(eid), "RC", rc);
+        }
+        else
+        {
+            info(
+                "Sent QueryDeviceIdentifiers request, EID={EID}, retries remaining: {RETRY}",
+                "EID", unsigned(eid), "RETRY", unsigned(retriesLeft));
+        }
+    }
+}
+
+void InventoryManager::queryDeviceIdentifiers(
+    const int retriesLeft, mctp_eid_t eid, const pldm_msg* response,
+    size_t respMsgLen)
+{
+    if (response == nullptr || !respMsgLen)
+    {
+        if (retriesLeft > 0)
+        {
+            error("No response received for QueryDeviceIdentifiers, EID={EID}. "
+                  "We really want this. Retries remaining: {RETRY}",
+                  "EID", unsigned(eid), "RETRY", unsigned(retriesLeft));
+            discoverFDs({eid}, retriesLeft - 1);
+        }
+        else
+        {
+            error(
+                "No response received for QueryDeviceIdentifiers, EID={EID}. No more retries remaining. Womp womp.",
+                "EID", unsigned(eid));
+        }
+        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)
+    {
+        error(
+            "Decoding QueryDeviceIdentifiers response failed, EID={EID}, RC = {RC}",
+            "EID", unsigned(eid), "RC", rc);
+        return;
+    }
+
+    if (completionCode)
+    {
+        error(
+            "QueryDeviceIdentifiers response failed with error completion code, EID={EID}, CC = {CC}",
+            "EID", unsigned(eid), "CC", unsigned(completionCode));
+        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)
+        {
+            error(
+                "Decoding descriptor type, length and value failed, EID={EID}, RC = {RC}",
+                "EID", unsigned(eid), "RC", rc);
+            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)
+            {
+                error(
+                    "Decoding Vendor-defined descriptor value failed, EID={EID}, RC = {RC}",
+                    "EID", unsigned(eid), "RC", rc);
+                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;
+    }
+
+    info("EID: {EID} Descriptors:", "EID", unsigned(eid));
+    for (const auto& [descriptor_type, descriptor] : descriptors)
+    {
+        if (std::holds_alternative<DescriptorData>(descriptor))
+        {
+            const auto desc = std::get<DescriptorData>(descriptor);
+            std::stringstream ss;
+            ss << std::hex << std::setfill('0');
+            for (const auto& byte : desc)
+            {
+                ss << std::setw(2) << static_cast<int>(byte);
+            }
+            info("Descriptor type: {TYPE}, descriptor: {DESC}", "TYPE",
+                 descriptor_type, "DESC", ss.str());
+        }
+        else
+        {
+            const auto [desc_title, desc] =
+                std::get<VendorDefinedDescriptorInfo>(descriptor);
+            std::stringstream ss;
+            ss << std::hex << std::setfill('0');
+            for (const auto& byte : desc)
+            {
+                ss << std::setw(2) << static_cast<int>(byte);
+            }
+            info("Descriptor type: {TYPE}, descriptor: {DESC_TITLE} {DESC}",
+                 "TYPE", descriptor_type, "DESC_TITLE", desc_title, "DESC",
+                 ss.str());
+        }
+    }
+    descriptorMap.emplace(eid, std::move(descriptors));
+
+    // Send GetFirmwareParameters request
+    sendGetFirmwareParametersRequest(retriesLeft, eid);
+}
+
+void InventoryManager::sendGetFirmwareParametersRequest(const int retriesLeft,
+                                                        mctp_eid_t eid)
+{
+    auto instanceIdResult = instanceIdDb.next(eid);
+    if (!instanceIdResult)
+    {
+        error("Instance ID allocation failed for EID {EID}", "EID",
+              unsigned(eid));
+        throw pldm::InstanceIdError(instanceIdResult.error());
+    }
+    auto instanceId = instanceIdResult.value();
+    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)
+    {
+        instanceIdDb.free(eid, instanceId);
+        error("encode_get_firmware_parameters_req failed, EID={EID}, RC = {RC}",
+              "EID", unsigned(eid), "RC", rc);
+        return;
+    }
+
+    rc = handler.registerRequest(
+        eid, instanceId, PLDM_FWUP, PLDM_GET_FIRMWARE_PARAMETERS,
+        std::move(requestMsg),
+        std::move(std::bind_front(&InventoryManager::getFirmwareParameters,
+                                  this, retriesLeft)));
+    if (rc)
+    {
+        error(
+            "Failed to send GetFirmwareParameters request, EID={EID}, RC = {RC}",
+            "EID", unsigned(eid), "RC", rc);
+    }
+    else
+    {
+        info(
+            "Sent GetFirmwareParameters request, EID={EID}, retries remaining: {RETRY}",
+            "EID", unsigned(eid), "RETRY", unsigned(retriesLeft));
+    }
+}
+
+void InventoryManager::getFirmwareParameters(
+    const int retriesLeft, mctp_eid_t eid, const pldm_msg* response,
+    size_t respMsgLen)
+{
+    if (response == nullptr || !respMsgLen)
+    {
+        if (retriesLeft > 0)
+        {
+            error("No response received for GetFirmwareParameters, EID={EID}. "
+                  "We really want this. Retries remaining: {RETRY}",
+                  "EID", unsigned(eid), "RETRY", unsigned(retriesLeft));
+            sendGetFirmwareParametersRequest(retriesLeft - 1, eid);
+        }
+        else
+        {
+            error(
+                "No response received for GetFirmwareParameters, EID={EID}. No more retries remaining. Womp womp.",
+                "EID", unsigned(eid));
+        }
+        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)
+    {
+        error(
+            "Decoding GetFirmwareParameters response failed, EID={EID}, RC = {RC}",
+            "EID", unsigned(eid), "RC", rc);
+        return;
+    }
+
+    if (fwParams.completion_code)
+    {
+        error(
+            "GetFirmwareParameters response failed with error completion code, EID={EID}, CC = {CC}",
+            "EID", unsigned(eid), "CC", unsigned(fwParams.completion_code));
+        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)
+        {
+            error(
+                "Decoding component parameter table entry failed, EID={EID}, RC = {RC}",
+                "EID", unsigned(eid), "RC", rc);
+            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;
+    }
+    info("EID: {EID} Components:", "EID", unsigned(eid));
+    for (const auto& [comp_key, comp_class_index] : componentInfo)
+    {
+        const auto& [comp_class, comp_identifier] = comp_key;
+        info(
+            "Classification: {CLASS} Identifier: {IDENT} Classification index: {CLASS_INDEX}",
+            "CLASS", unsigned(comp_class), "IDENT", unsigned(comp_identifier),
+            "CLASS_INDEX", unsigned(comp_class_index));
+    }
+    componentInfoMap.emplace(eid, std::move(componentInfo));
+}
+
+} // namespace fw_update
+
+} // namespace pldm
diff --git a/fw-update-legacy/inventory_manager.hpp b/fw-update-legacy/inventory_manager.hpp
new file mode 100644
index 0000000..b879fa4
--- /dev/null
+++ b/fw-update-legacy/inventory_manager.hpp
@@ -0,0 +1,109 @@
+#pragma once
+
+#include "common/instance_id.hpp"
+#include "common/types.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] instanceIdDb - 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,
+        InstanceIdDb& instanceIdDb, DescriptorMap& descriptorMap,
+        ComponentInfoMap& componentInfoMap) :
+        handler(handler), instanceIdDb(instanceIdDb),
+        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
+     *  @param[in] retriesLeft - How many retry attempts are left.
+     */
+    void discoverFDs(const std::vector<mctp_eid_t>& eids, int retriesLeft);
+
+    /** @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] retriesLeft - How many retry attempts are left.
+     *  @param[in] eid - Remote MCTP endpoint
+     *  @param[in] response - PLDM response message
+     *  @param[in] respMsgLen - Response message length
+     */
+    void queryDeviceIdentifiers(int retriesLeft, 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] retriesLeft - How many retry attempts are left.
+     *  @param[in] eid - Remote MCTP endpoint
+     *  @param[in] response - PLDM response message
+     *  @param[in] respMsgLen - Response message length
+     */
+    void getFirmwareParameters(int retriesLeft, mctp_eid_t eid,
+                               const pldm_msg* response, size_t respMsgLen);
+
+  private:
+    /** @brief Send GetFirmwareParameters command request
+     *
+     *  @param[in] retriesLeft - How many retry attempts are left.
+     *  @param[in] eid - Remote MCTP endpoint
+     */
+    void sendGetFirmwareParametersRequest(int retriesLeft, mctp_eid_t eid);
+
+    /** @brief PLDM request handler */
+    pldm::requester::Handler<pldm::requester::Request>& handler;
+
+    /** @brief Instance ID database for managing instance ID*/
+    InstanceIdDb& instanceIdDb;
+
+    /** @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-legacy/manager.hpp b/fw-update-legacy/manager.hpp
new file mode 100644
index 0000000..649bf20
--- /dev/null
+++ b/fw-update-legacy/manager.hpp
@@ -0,0 +1,158 @@
+#pragma once
+
+#include "config.h"
+
+#include "activation.hpp"
+#include "common/instance_id.hpp"
+#include "common/types.hpp"
+#include "device_updater.hpp"
+#include "inventory_manager.hpp"
+#include "requester/handler.hpp"
+#include "requester/mctp_endpoint_discovery.hpp"
+#include "update_manager.hpp"
+
+#include <unordered_map>
+#include <vector>
+
+namespace pldm
+{
+
+namespace fw_update
+{
+
+constexpr uint8_t FW_UPDATE_DISCOVERY_RETRIES = 2;
+
+/** @class Manager
+ *
+ * This class handles all the aspects of the PLDM FW update specification for
+ * the MCTP devices
+ */
+class Manager : public pldm::MctpDiscoveryHandlerIntf
+{
+  public:
+    Manager() = delete;
+    Manager(const Manager&) = delete;
+    Manager(Manager&&) = delete;
+    Manager& operator=(const Manager&) = delete;
+    Manager& operator=(Manager&&) = delete;
+    ~Manager() = default;
+
+    /** @brief Constructor
+     *
+     *  @param[in] handler - PLDM request handler
+     */
+    explicit Manager(Event& event,
+                     requester::Handler<requester::Request>& handler,
+                     pldm::InstanceIdDb& instanceIdDb) :
+        handler(handler),
+        inventoryMgr(handler, instanceIdDb, descriptorMap, componentInfoMap),
+        updateManager(event, handler, instanceIdDb, descriptorMap,
+                      componentInfoMap)
+    {}
+
+    /** @brief Helper function to invoke registered handlers for
+     *         the added MCTP endpoints
+     *
+     *  @param[in] mctpInfos - information of discovered MCTP endpoints
+     */
+    void handleMctpEndpoints(const TerminusInfos& mctpInfos) override
+    {
+        std::vector<mctp_eid_t> eids;
+        for (const auto& [tid, mctpInfo] : mctpInfos)
+        {
+            auto eid = std::get<pldm::eid>(mctpInfo);
+            auto network = std::get<NetworkId>(mctpInfo);
+
+            if (auto* transport = handler.getTransport())
+            {
+                transport->mapTid(tid, network, eid);
+            }
+            eids.push_back(eid);
+        }
+        inventoryMgr.discoverFDs(eids, FW_UPDATE_DISCOVERY_RETRIES);
+    }
+
+    /** @brief Helper function to invoke registered handlers for
+     *         the removed MCTP endpoints
+     *
+     *  @param[in] mctpInfos - information of removed MCTP endpoints
+     */
+    void handleRemovedMctpEndpoints(const TerminusInfos& mctpInfos) override
+    {
+        for (const auto& [tid, mctpInfo] : mctpInfos)
+        {
+            if (auto* transport = handler.getTransport())
+            {
+                transport->unmapTid(tid);
+            }
+        }
+    }
+
+    /** @brief Helper function to invoke registered handlers for
+     *  updating the availability status of the MCTP endpoint
+     *
+     *  @param[in] mctpInfo - information of the target endpoint
+     *  @param[in] availability - new availability status
+     */
+    void updateMctpEndpointAvailability(const MctpInfo&, Availability) override
+    {
+        return;
+    }
+
+    /** @brief Get Active EIDs.
+     *
+     *  @param[in] addr - MCTP address of terminus
+     *  @param[in] terminiNames - MCTP terminus name
+     */
+    std::optional<mctp_eid_t> getActiveEidByName(const std::string&) override
+    {
+        return std::nullopt;
+    }
+
+    std::optional<pldm_tid_t> allocateOrGetTid(
+        const MctpInfo& /*mctpInfo*/) override
+    {
+        // FW update doesn't manage TIDs
+        return std::nullopt;
+    }
+
+    PldmTransport* getTransport() override
+    {
+        return handler.getTransport();
+    }
+
+    /** @brief Handle PLDM request for the commands in the FW update
+     *         specification
+     *
+     *  @param[in] eid - Remote MCTP Endpoint ID
+     *  @param[in] command - PLDM command code
+     *  @param[in] request - PLDM request message
+     *  @param[in] requestLen - PLDM request message length
+     *  @return PLDM response message
+     */
+    Response handleRequest(mctp_eid_t eid, Command command,
+                           const pldm_msg* request, size_t reqMsgLen)
+    {
+        return updateManager.handleRequest(eid, command, request, reqMsgLen);
+    }
+
+  private:
+    /** @brief Reference to the PLDM request handler */
+    requester::Handler<requester::Request>& handler;
+
+    /** Descriptor information of all the discovered MCTP endpoints */
+    DescriptorMap descriptorMap;
+
+    /** Component information of all the discovered MCTP endpoints */
+    ComponentInfoMap componentInfoMap;
+
+    /** @brief PLDM firmware inventory manager */
+    InventoryManager inventoryMgr;
+
+    /** @brief PLDM firmware update manager */
+    UpdateManager updateManager;
+};
+
+} // namespace fw_update
+
+} // namespace pldm
diff --git a/fw-update-legacy/package_parser.cpp b/fw-update-legacy/package_parser.cpp
new file mode 100644
index 0000000..8381acc
--- /dev/null
+++ b/fw-update-legacy/package_parser.cpp
@@ -0,0 +1,325 @@
+#include "package_parser.hpp"
+
+#include "common/utils.hpp"
+
+#include <libpldm/edac.h>
+#include <libpldm/firmware_update.h>
+
+#include <phosphor-logging/lg2.hpp>
+#include <xyz/openbmc_project/Common/error.hpp>
+
+#include <iostream>
+#include <memory>
+
+PHOSPHOR_LOG2_USING;
+
+namespace pldm
+{
+
+namespace fw_update
+{
+
+using InternalFailure =
+    sdbusplus::xyz::openbmc_project::Common::Error::InternalFailure;
+
+size_t PackageParser::parseFDIdentificationArea(
+    DeviceIDRecordCount deviceIdRecCount, const std::vector<uint8_t>& pkgHdr,
+    size_t offset)
+{
+    size_t pkgHdrRemainingSize = pkgHdr.size() - offset;
+
+    while (deviceIdRecCount-- && (pkgHdrRemainingSize > 0))
+    {
+        pldm_firmware_device_id_record deviceIdRecHeader{};
+        variable_field applicableComponents{};
+        variable_field compImageSetVersionStr{};
+        variable_field recordDescriptors{};
+        variable_field fwDevicePkgData{};
+
+        auto rc = decode_firmware_device_id_record(
+            pkgHdr.data() + offset, pkgHdrRemainingSize,
+            componentBitmapBitLength, &deviceIdRecHeader, &applicableComponents,
+            &compImageSetVersionStr, &recordDescriptors, &fwDevicePkgData);
+        if (rc)
+        {
+            error("Decoding firmware device ID record failed, RC={RC}", "RC",
+                  rc);
+            throw InternalFailure();
+        }
+
+        Descriptors descriptors{};
+        while (deviceIdRecHeader.descriptor_count-- &&
+               (recordDescriptors.length > 0))
+        {
+            uint16_t descriptorType = 0;
+            variable_field descriptorData{};
+
+            rc = decode_descriptor_type_length_value(
+                recordDescriptors.ptr, recordDescriptors.length,
+                &descriptorType, &descriptorData);
+            if (rc)
+            {
+                error(
+                    "Decoding descriptor type, length and value failed, RC={RC}",
+                    "RC", rc);
+                throw InternalFailure();
+            }
+
+            if (descriptorType != PLDM_FWUP_VENDOR_DEFINED)
+            {
+                descriptors.emplace(
+                    descriptorType,
+                    DescriptorData{descriptorData.ptr,
+                                   descriptorData.ptr + descriptorData.length});
+            }
+            else
+            {
+                uint8_t descTitleStrType = 0;
+                variable_field descTitleStr{};
+                variable_field vendorDefinedDescData{};
+
+                rc = decode_vendor_defined_descriptor_value(
+                    descriptorData.ptr, descriptorData.length,
+                    &descTitleStrType, &descTitleStr, &vendorDefinedDescData);
+                if (rc)
+                {
+                    error(
+                        "Decoding Vendor-defined descriptor value failed, RC={RC}",
+                        "RC", rc);
+                    throw InternalFailure();
+                }
+
+                descriptors.emplace(
+                    descriptorType,
+                    std::make_tuple(utils::toString(descTitleStr),
+                                    VendorDefinedDescriptorData{
+                                        vendorDefinedDescData.ptr,
+                                        vendorDefinedDescData.ptr +
+                                            vendorDefinedDescData.length}));
+            }
+
+            auto nextDescriptorOffset =
+                sizeof(pldm_descriptor_tlv().descriptor_type) +
+                sizeof(pldm_descriptor_tlv().descriptor_length) +
+                descriptorData.length;
+            recordDescriptors.ptr += nextDescriptorOffset;
+            recordDescriptors.length -= nextDescriptorOffset;
+        }
+
+        DeviceUpdateOptionFlags deviceUpdateOptionFlags =
+            deviceIdRecHeader.device_update_option_flags.value;
+
+        ApplicableComponents componentsList;
+
+        for (size_t varBitfieldIdx = 0;
+             varBitfieldIdx < applicableComponents.length; varBitfieldIdx++)
+        {
+            std::bitset<8> entry{*(applicableComponents.ptr + varBitfieldIdx)};
+            for (size_t idx = 0; idx < entry.size(); idx++)
+            {
+                if (entry[idx])
+                {
+                    componentsList.emplace_back(
+                        idx + (varBitfieldIdx * entry.size()));
+                }
+            }
+        }
+
+        fwDeviceIDRecords.emplace_back(std::make_tuple(
+            deviceUpdateOptionFlags, componentsList,
+            utils::toString(compImageSetVersionStr), std::move(descriptors),
+            FirmwareDevicePackageData{
+                fwDevicePkgData.ptr,
+                fwDevicePkgData.ptr + fwDevicePkgData.length}));
+        offset += deviceIdRecHeader.record_length;
+        pkgHdrRemainingSize -= deviceIdRecHeader.record_length;
+    }
+
+    return offset;
+}
+
+size_t PackageParser::parseCompImageInfoArea(ComponentImageCount compImageCount,
+                                             const std::vector<uint8_t>& pkgHdr,
+                                             size_t offset)
+{
+    size_t pkgHdrRemainingSize = pkgHdr.size() - offset;
+
+    while (compImageCount-- && (pkgHdrRemainingSize > 0))
+    {
+        pldm_component_image_information compImageInfo{};
+        variable_field compVersion{};
+
+        auto rc = decode_pldm_comp_image_info(
+            pkgHdr.data() + offset, pkgHdrRemainingSize, &compImageInfo,
+            &compVersion);
+        if (rc)
+        {
+            error("Decoding component image information failed, RC={RC}", "RC",
+                  rc);
+            throw InternalFailure();
+        }
+
+        CompClassification compClassification =
+            compImageInfo.comp_classification;
+        CompIdentifier compIdentifier = compImageInfo.comp_identifier;
+        CompComparisonStamp compComparisonTime =
+            compImageInfo.comp_comparison_stamp;
+        CompOptions compOptions = compImageInfo.comp_options.value;
+        ReqCompActivationMethod reqCompActivationMethod =
+            compImageInfo.requested_comp_activation_method.value;
+        CompLocationOffset compLocationOffset =
+            compImageInfo.comp_location_offset;
+        CompSize compSize = compImageInfo.comp_size;
+
+        componentImageInfos.emplace_back(std::make_tuple(
+            compClassification, compIdentifier, compComparisonTime, compOptions,
+            reqCompActivationMethod, compLocationOffset, compSize,
+            utils::toString(compVersion)));
+        offset += sizeof(pldm_component_image_information) +
+                  compImageInfo.comp_version_string_length;
+        pkgHdrRemainingSize -= sizeof(pldm_component_image_information) +
+                               compImageInfo.comp_version_string_length;
+    }
+
+    return offset;
+}
+
+void PackageParser::validatePkgTotalSize(uintmax_t pkgSize)
+{
+    uintmax_t calcPkgSize = pkgHeaderSize;
+    for (const auto& componentImageInfo : componentImageInfos)
+    {
+        CompLocationOffset compLocOffset = std::get<static_cast<size_t>(
+            ComponentImageInfoPos::CompLocationOffsetPos)>(componentImageInfo);
+        CompSize compSize =
+            std::get<static_cast<size_t>(ComponentImageInfoPos::CompSizePos)>(
+                componentImageInfo);
+
+        if (compLocOffset != calcPkgSize)
+        {
+            auto cmpVersion = std::get<static_cast<size_t>(
+                ComponentImageInfoPos::CompVersionPos)>(componentImageInfo);
+            error(
+                "Validating the component location offset failed, COMP_VERSION={COMP_VERS}",
+                "COMP_VERS", cmpVersion);
+            throw InternalFailure();
+        }
+
+        calcPkgSize += compSize;
+    }
+
+    if (calcPkgSize != pkgSize)
+    {
+        error(
+            "Package size does not match calculated package size, PKG_SIZE={PKG_SIZE}, CALC_PKG_SIZE={CAL_PKG_SIZE}",
+            "PKG_SIZE", pkgSize, "CAL_PKG_SIZE", calcPkgSize);
+        throw InternalFailure();
+    }
+}
+
+void PackageParserV1::parse(const std::vector<uint8_t>& pkgHdr,
+                            uintmax_t pkgSize)
+{
+    if (pkgHeaderSize != pkgHdr.size())
+    {
+        error("Package header size is invalid, PKG_HDR_SIZE={PKG_HDR_SIZE}",
+              "PKG_HDR_SIZE", pkgHeaderSize);
+        throw InternalFailure();
+    }
+
+    size_t offset = sizeof(pldm_package_header_information) + pkgVersion.size();
+    if (offset + sizeof(DeviceIDRecordCount) >= pkgHeaderSize)
+    {
+        error("Parsing package header failed, PKG_HDR_SIZE={PKG_HDR_SIZE}",
+              "PKG_HDR_SIZE", pkgHeaderSize);
+        throw InternalFailure();
+    }
+
+    auto deviceIdRecCount = static_cast<DeviceIDRecordCount>(pkgHdr[offset]);
+    offset += sizeof(DeviceIDRecordCount);
+
+    offset = parseFDIdentificationArea(deviceIdRecCount, pkgHdr, offset);
+    if (deviceIdRecCount != fwDeviceIDRecords.size())
+    {
+        error(
+            "DeviceIDRecordCount entries not found, DEVICE_ID_REC_COUNT={DREC_CNT}",
+            "DREC_CNT", deviceIdRecCount);
+        throw InternalFailure();
+    }
+    if (offset + sizeof(ComponentImageCount) >= pkgHeaderSize)
+    {
+        error("Parsing package header failed, PKG_HDR_SIZE={PKG_HDR_SIZE}",
+              "PKG_HDR_SIZE", pkgHeaderSize);
+        throw InternalFailure();
+    }
+
+    auto compImageCount = static_cast<ComponentImageCount>(
+        le16toh(pkgHdr[offset] | (pkgHdr[offset + 1] << 8)));
+    offset += sizeof(ComponentImageCount);
+
+    offset = parseCompImageInfoArea(compImageCount, pkgHdr, offset);
+    if (compImageCount != componentImageInfos.size())
+    {
+        error(
+            "ComponentImageCount entries not found, COMP_IMAGE_COUNT={COMP_IMG_CNT}",
+            "COMP_IMG_CNT", compImageCount);
+        throw InternalFailure();
+    }
+
+    if (offset + sizeof(PackageHeaderChecksum) != pkgHeaderSize)
+    {
+        error("Parsing package header failed, PKG_HDR_SIZE={PKG_HDR_SIZE}",
+              "PKG_HDR_SIZE", pkgHeaderSize);
+        throw InternalFailure();
+    }
+
+    auto calcChecksum = pldm_edac_crc32(pkgHdr.data(), offset);
+    auto checksum = static_cast<PackageHeaderChecksum>(
+        le32toh(pkgHdr[offset] | (pkgHdr[offset + 1] << 8) |
+                (pkgHdr[offset + 2] << 16) | (pkgHdr[offset + 3] << 24)));
+    if (calcChecksum != checksum)
+    {
+        error(
+            "Parsing package header failed, CALC_CHECKSUM={CHK_SUM}, PKG_HDR_CHECKSUM={PKG_HDR_CHK_SUM}",
+            "CHK_SUM", calcChecksum, "PKG_HDR_CHK_SUM", checksum);
+        throw InternalFailure();
+    }
+
+    validatePkgTotalSize(pkgSize);
+}
+
+std::unique_ptr<PackageParser> parsePkgHeader(std::vector<uint8_t>& pkgData)
+{
+    constexpr std::array<uint8_t, PLDM_FWUP_UUID_LENGTH> hdrIdentifierv1{
+        0xF0, 0x18, 0x87, 0x8C, 0xCB, 0x7D, 0x49, 0x43,
+        0x98, 0x00, 0xA0, 0x2F, 0x05, 0x9A, 0xCA, 0x02};
+    constexpr uint8_t pkgHdrVersion1 = 0x01;
+
+    pldm_package_header_information pkgHeader{};
+    variable_field pkgVersion{};
+    auto rc = decode_pldm_package_header_info(pkgData.data(), pkgData.size(),
+                                              &pkgHeader, &pkgVersion);
+    if (rc)
+    {
+        error("Decoding PLDM package header information failed, RC={RC}", "RC",
+              rc);
+        return nullptr;
+    }
+
+    if (std::equal(pkgHeader.uuid, pkgHeader.uuid + PLDM_FWUP_UUID_LENGTH,
+                   hdrIdentifierv1.begin(), hdrIdentifierv1.end()) &&
+        (pkgHeader.package_header_format_version == pkgHdrVersion1))
+    {
+        PackageHeaderSize pkgHdrSize = pkgHeader.package_header_size;
+        ComponentBitmapBitLength componentBitmapBitLength =
+            pkgHeader.component_bitmap_bit_length;
+        return std::make_unique<PackageParserV1>(
+            pkgHdrSize, utils::toString(pkgVersion), componentBitmapBitLength);
+    }
+
+    return nullptr;
+}
+
+} // namespace fw_update
+
+} // namespace pldm
diff --git a/fw-update-legacy/package_parser.hpp b/fw-update-legacy/package_parser.hpp
new file mode 100644
index 0000000..5578916
--- /dev/null
+++ b/fw-update-legacy/package_parser.hpp
@@ -0,0 +1,187 @@
+#pragma once
+
+#include "common/types.hpp"
+
+#include <libpldm/firmware_update.h>
+
+#include <array>
+#include <cstdint>
+#include <memory>
+#include <tuple>
+#include <vector>
+
+namespace pldm
+{
+
+namespace fw_update
+{
+
+/** @class PackageParser
+ *
+ *  PackageParser is the abstract base class for parsing the PLDM firmware
+ *  update package. The PLDM firmware update contains two major sections; the
+ *  firmware package header, and the firmware package payload. Each package
+ *  header version will have a concrete implementation of the PackageParser.
+ *  The concrete implementation understands the format of the package header and
+ *  will implement the parse API.
+ */
+class PackageParser
+{
+  public:
+    PackageParser() = delete;
+    PackageParser(const PackageParser&) = delete;
+    PackageParser(PackageParser&&) = default;
+    PackageParser& operator=(const PackageParser&) = delete;
+    PackageParser& operator=(PackageParser&&) = delete;
+    virtual ~PackageParser() = default;
+
+    /** @brief Constructor
+     *
+     *  @param[in] pkgHeaderSize - Size of package header section
+     *  @param[in] pkgVersion - Package version
+     *  @param[in] componentBitmapBitLength - The number of bits used to
+     *                                        represent the bitmap in the
+     *                                        ApplicableComponents field for a
+     *                                        matching device.
+     */
+    explicit PackageParser(PackageHeaderSize pkgHeaderSize,
+                           const PackageVersion& pkgVersion,
+                           ComponentBitmapBitLength componentBitmapBitLength) :
+        pkgHeaderSize(pkgHeaderSize), pkgVersion(pkgVersion),
+        componentBitmapBitLength(componentBitmapBitLength)
+    {}
+
+    /** @brief Parse the firmware update package header
+     *
+     *  @param[in] pkgHdr - Package header
+     *  @param[in] pkgSize - Size of the firmware update package
+     *
+     *  @note Throws exception is parsing fails
+     */
+    virtual void parse(const std::vector<uint8_t>& pkgHdr,
+                       uintmax_t pkgSize) = 0;
+
+    /** @brief Get firmware device ID records from the package
+     *
+     *  @return if parsing the package is successful, return firmware device ID
+     *          records
+     */
+    const FirmwareDeviceIDRecords& getFwDeviceIDRecords() const
+    {
+        return fwDeviceIDRecords;
+    }
+
+    /** @brief Get component image information from the package
+     *
+     *  @return if parsing the package is successful, return component image
+     *          information
+     */
+    const ComponentImageInfos& getComponentImageInfos() const
+    {
+        return componentImageInfos;
+    }
+
+    /** @brief Device identifiers of the managed FDs */
+    const PackageHeaderSize pkgHeaderSize;
+
+    /** @brief Package version string */
+    const PackageVersion pkgVersion;
+
+  protected:
+    /** @brief Parse the firmware device identification area
+     *
+     *  @param[in] deviceIdRecCount - count of firmware device ID records
+     *  @param[in] pkgHdr - firmware package header
+     *  @param[in] offset - offset in package header which is the start of the
+     *                      firmware device identification area
+     *
+     *  @return On success return the offset which is the end of the firmware
+     *          device identification area, on error throw exception.
+     */
+    size_t parseFDIdentificationArea(DeviceIDRecordCount deviceIdRecCount,
+                                     const std::vector<uint8_t>& pkgHdr,
+                                     size_t offset);
+
+    /** @brief Parse the component image information area
+     *
+     *  @param[in] compImageCount - component image count
+     *  @param[in] pkgHdr - firmware package header
+     *  @param[in] offset - offset in package header which is the start of the
+     *                      component image information area
+     *
+     *  @return On success return the offset which is the end of the component
+     *          image information area, on error throw exception.
+     */
+    size_t parseCompImageInfoArea(ComponentImageCount compImageCount,
+                                  const std::vector<uint8_t>& pkgHdr,
+                                  size_t offset);
+
+    /** @brief Validate the total size of the package
+     *
+     *  Verify the total size of the package is the sum of package header and
+     *  the size of each component.
+     *
+     *  @param[in] pkgSize - firmware update package size
+     *
+     *  @note Throws exception if validation fails
+     */
+    void validatePkgTotalSize(uintmax_t pkgSize);
+
+    /** @brief Firmware Device ID Records in the package */
+    FirmwareDeviceIDRecords fwDeviceIDRecords;
+
+    /** @brief Component Image Information in the package */
+    ComponentImageInfos componentImageInfos;
+
+    /** @brief The number of bits that will be used to represent the bitmap in
+     *         the ApplicableComponents field for matching device. The value
+     *         shall be a multiple of 8 and be large enough to contain a bit
+     *         for each component in the package.
+     */
+    const ComponentBitmapBitLength componentBitmapBitLength;
+};
+
+/** @class PackageParserV1
+ *
+ *  This class implements the package parser for the header format version 0x01
+ */
+class PackageParserV1 final : public PackageParser
+{
+  public:
+    PackageParserV1() = delete;
+    PackageParserV1(const PackageParserV1&) = delete;
+    PackageParserV1(PackageParserV1&&) = default;
+    PackageParserV1& operator=(const PackageParserV1&) = delete;
+    PackageParserV1& operator=(PackageParserV1&&) = delete;
+    ~PackageParserV1() = default;
+
+    /** @brief Constructor
+     *
+     *  @param[in] pkgHeaderSize - Size of package header section
+     *  @param[in] pkgVersion - Package version
+     *  @param[in] componentBitmapBitLength - The number of bits used to
+     *                                        represent the bitmap in the
+     *                                        ApplicableComponents field for a
+     *                                        matching device.
+     */
+    explicit PackageParserV1(
+        PackageHeaderSize pkgHeaderSize, const PackageVersion& pkgVersion,
+        ComponentBitmapBitLength componentBitmapBitLength) :
+        PackageParser(pkgHeaderSize, pkgVersion, componentBitmapBitLength)
+    {}
+
+    virtual void parse(const std::vector<uint8_t>& pkgHdr, uintmax_t pkgSize);
+};
+
+/** @brief Parse the package header information
+ *
+ *  @param[in] pkgHdrInfo - package header information section in the package
+ *
+ *  @return On success return the PackageParser for the header format version
+ *          on failure return nullptr
+ */
+std::unique_ptr<PackageParser> parsePkgHeader(std::vector<uint8_t>& pkgHdrInfo);
+
+} // namespace fw_update
+
+} // namespace pldm
diff --git a/fw-update-legacy/test/device_updater_test.cpp b/fw-update-legacy/test/device_updater_test.cpp
new file mode 100644
index 0000000..f4aac0b
--- /dev/null
+++ b/fw-update-legacy/test/device_updater_test.cpp
@@ -0,0 +1,152 @@
+#include "common/instance_id.hpp"
+#include "common/utils.hpp"
+#include "fw-update-legacy/device_updater.hpp"
+#include "fw-update-legacy/package_parser.hpp"
+#include "requester/handler.hpp"
+
+#include <libpldm/firmware_update.h>
+
+#include <gmock/gmock.h>
+#include <gtest/gtest.h>
+
+using namespace pldm;
+using namespace pldm::fw_update;
+
+class DeviceUpdaterTest : public testing::Test
+{
+  protected:
+    DeviceUpdaterTest() :
+        package("./test_pkg", std::ios::binary | std::ios::in | std::ios::ate)
+    {
+        fwDeviceIDRecord = {
+            1,
+            {0x00},
+            "VersionString2",
+            {{PLDM_FWUP_UUID,
+              std::vector<uint8_t>{0x16, 0x20, 0x23, 0xC9, 0x3E, 0xC5, 0x41,
+                                   0x15, 0x95, 0xF4, 0x48, 0x70, 0x1D, 0x49,
+                                   0xD6, 0x75}}},
+            {}};
+        compImageInfos = {
+            {10, 100, 0xFFFFFFFF, 0, 0, 139, 1024, "VersionString3"}};
+        compInfo = {{std::make_pair(10, 100), 1}};
+    }
+
+    int fd = -1;
+    std::ifstream package;
+    FirmwareDeviceIDRecord fwDeviceIDRecord;
+    ComponentImageInfos compImageInfos;
+    ComponentInfo compInfo;
+};
+
+TEST_F(DeviceUpdaterTest, validatePackage)
+{
+    constexpr uintmax_t testPkgSize = 1163;
+    uintmax_t packageSize = package.tellg();
+    EXPECT_EQ(packageSize, testPkgSize);
+
+    package.seekg(0);
+    std::vector<uint8_t> packageHeader(sizeof(pldm_package_header_information));
+    package.read(reinterpret_cast<char*>(packageHeader.data()),
+                 sizeof(pldm_package_header_information));
+
+    auto pkgHeaderInfo =
+        reinterpret_cast<const pldm_package_header_information*>(
+            packageHeader.data());
+    auto pkgHeaderInfoSize = sizeof(pldm_package_header_information) +
+                             pkgHeaderInfo->package_version_string_length;
+    packageHeader.clear();
+    packageHeader.resize(pkgHeaderInfoSize);
+    package.seekg(0);
+    package.read(reinterpret_cast<char*>(packageHeader.data()),
+                 pkgHeaderInfoSize);
+
+    auto parser = parsePkgHeader(packageHeader);
+    EXPECT_NE(parser, nullptr);
+
+    package.seekg(0);
+    packageHeader.resize(parser->pkgHeaderSize);
+    package.read(reinterpret_cast<char*>(packageHeader.data()),
+                 parser->pkgHeaderSize);
+
+    parser->parse(packageHeader, packageSize);
+    const auto& fwDeviceIDRecords = parser->getFwDeviceIDRecords();
+    const auto& testPkgCompImageInfos = parser->getComponentImageInfos();
+
+    EXPECT_EQ(fwDeviceIDRecords.size(), 1);
+    EXPECT_EQ(compImageInfos.size(), 1);
+    EXPECT_EQ(fwDeviceIDRecords[0], fwDeviceIDRecord);
+    EXPECT_EQ(testPkgCompImageInfos, compImageInfos);
+}
+
+TEST_F(DeviceUpdaterTest, ReadPackage512B)
+{
+    DeviceUpdater deviceUpdater(0, package, fwDeviceIDRecord, compImageInfos,
+                                compInfo, 512, nullptr);
+
+    constexpr std::array<uint8_t, sizeof(pldm_msg_hdr) +
+                                      sizeof(pldm_request_firmware_data_req)>
+        reqFwDataReq{0x8A, 0x05, 0x15, 0x00, 0x00, 0x00,
+                     0x00, 0x00, 0x02, 0x00, 0x00};
+    constexpr uint8_t instanceId = 0x0A;
+    constexpr uint8_t completionCode = PLDM_SUCCESS;
+    constexpr uint32_t length = 512;
+    auto requestMsg = reinterpret_cast<const pldm_msg*>(reqFwDataReq.data());
+    auto response = deviceUpdater.requestFwData(
+        requestMsg, sizeof(pldm_request_firmware_data_req));
+
+    EXPECT_EQ(response.size(),
+              sizeof(pldm_msg_hdr) + sizeof(completionCode) + length);
+    auto responeMsg = reinterpret_cast<const pldm_msg*>(response.data());
+    EXPECT_EQ(responeMsg->hdr.request, PLDM_RESPONSE);
+    EXPECT_EQ(responeMsg->hdr.instance_id, instanceId);
+    EXPECT_EQ(responeMsg->hdr.type, PLDM_FWUP);
+    EXPECT_EQ(responeMsg->hdr.command, PLDM_REQUEST_FIRMWARE_DATA);
+    EXPECT_EQ(response[sizeof(pldm_msg_hdr)], completionCode);
+
+    const std::vector<uint8_t> compFirst512B{
+        0x0A, 0x05, 0x15, 0x00, 0x48, 0xD2, 0x1E, 0x80, 0x2E, 0x77, 0x71, 0x2C,
+        0x8E, 0xE3, 0x1F, 0x6F, 0x30, 0x76, 0x65, 0x08, 0xB8, 0x1B, 0x4B, 0x03,
+        0x7E, 0x96, 0xD9, 0x2A, 0x36, 0x3A, 0xA2, 0xEE, 0x8A, 0x30, 0x21, 0x33,
+        0xFC, 0x27, 0xE7, 0x3E, 0x56, 0x79, 0x0E, 0xBD, 0xED, 0x44, 0x96, 0x2F,
+        0x84, 0xB5, 0xED, 0x19, 0x3A, 0x5E, 0x62, 0x2A, 0x6E, 0x41, 0x7E, 0xDC,
+        0x2E, 0xBB, 0x87, 0x41, 0x7F, 0xCE, 0xF0, 0xD7, 0xE4, 0x0F, 0x95, 0x33,
+        0x3B, 0xF9, 0x04, 0xF8, 0x1A, 0x92, 0x54, 0xFD, 0x33, 0xBA, 0xCD, 0xA6,
+        0x08, 0x0D, 0x32, 0x2C, 0xEB, 0x75, 0xDC, 0xEA, 0xBA, 0x30, 0x94, 0x78,
+        0x8C, 0x61, 0x58, 0xD0, 0x59, 0xF3, 0x29, 0x6D, 0x67, 0xD3, 0x26, 0x08,
+        0x25, 0x1E, 0x69, 0xBB, 0x28, 0xB0, 0x61, 0xFB, 0x96, 0xA3, 0x8C, 0xBF,
+        0x01, 0x94, 0xEB, 0x3A, 0x63, 0x6F, 0xC8, 0x0F, 0x42, 0x7F, 0xEB, 0x3D,
+        0xA7, 0x8B, 0xE5, 0xD2, 0xFB, 0xB8, 0xD3, 0x15, 0xAA, 0xDF, 0x86, 0xAB,
+        0x6E, 0x29, 0xB3, 0x12, 0x96, 0xB7, 0x86, 0xDA, 0xF9, 0xD7, 0x70, 0xAD,
+        0xB6, 0x1A, 0x29, 0xB1, 0xA4, 0x2B, 0x6F, 0x63, 0xEE, 0x05, 0x9F, 0x35,
+        0x49, 0xA1, 0xAB, 0xA2, 0x6F, 0x7C, 0xFC, 0x23, 0x09, 0x55, 0xED, 0xF7,
+        0x35, 0xD8, 0x2F, 0x8F, 0xD2, 0xBD, 0x77, 0xED, 0x0C, 0x7A, 0xE9, 0xD3,
+        0xF7, 0x90, 0xA7, 0x45, 0x97, 0xAA, 0x3A, 0x79, 0xC4, 0xF8, 0xD2, 0xFE,
+        0xFB, 0xB3, 0x25, 0x86, 0x98, 0x6B, 0x98, 0x10, 0x15, 0xB3, 0xDD, 0x43,
+        0x0B, 0x20, 0x5F, 0xE4, 0x62, 0xC8, 0xA1, 0x3E, 0x9C, 0xF3, 0xD8, 0xEA,
+        0x15, 0xA1, 0x24, 0x94, 0x1C, 0xF5, 0xB4, 0x86, 0x04, 0x30, 0x2C, 0x84,
+        0xB6, 0x29, 0xF6, 0x9D, 0x76, 0x6E, 0xD4, 0x0C, 0x1C, 0xBD, 0xF9, 0x95,
+        0x7E, 0xAF, 0x62, 0x80, 0x14, 0xE6, 0x1C, 0x43, 0x51, 0x5C, 0xCA, 0x50,
+        0xE1, 0x73, 0x3D, 0x75, 0x66, 0x52, 0x9E, 0xB6, 0x15, 0x7E, 0xF7, 0xE5,
+        0xE2, 0xAF, 0x54, 0x75, 0x82, 0x3D, 0x55, 0xC7, 0x59, 0xD7, 0xBD, 0x8C,
+        0x4B, 0x74, 0xD1, 0x3F, 0xA8, 0x1B, 0x0A, 0xF0, 0x5A, 0x32, 0x2B, 0xA7,
+        0xA4, 0xBE, 0x38, 0x18, 0xAE, 0x69, 0xDC, 0x54, 0x7C, 0x60, 0xEF, 0x4F,
+        0x0F, 0x7F, 0x5A, 0xA6, 0xC8, 0x3E, 0x59, 0xFD, 0xF5, 0x98, 0x26, 0x71,
+        0xD0, 0xEF, 0x54, 0x47, 0x38, 0x1F, 0x18, 0x9D, 0x37, 0x9D, 0xF0, 0xCD,
+        0x00, 0x73, 0x30, 0xD4, 0xB7, 0xDA, 0x2D, 0x36, 0xA1, 0xA9, 0xAD, 0x4F,
+        0x9F, 0x17, 0xA5, 0xA1, 0x62, 0x18, 0x21, 0xDD, 0x0E, 0xB6, 0x72, 0xDE,
+        0x17, 0xF0, 0x71, 0x94, 0xA9, 0x67, 0xB4, 0x75, 0xDB, 0x64, 0xF0, 0x6E,
+        0x3D, 0x4E, 0x29, 0x45, 0x42, 0xC3, 0xDA, 0x1F, 0x9E, 0x31, 0x4D, 0x1B,
+        0xA7, 0x9D, 0x07, 0xD9, 0x10, 0x75, 0x27, 0x92, 0x16, 0x35, 0xF5, 0x51,
+        0x3E, 0x14, 0x00, 0xB4, 0xBD, 0x21, 0xAF, 0x90, 0xC5, 0xE5, 0xEE, 0xD0,
+        0xB3, 0x7F, 0x61, 0xA5, 0x1B, 0x91, 0xD5, 0x66, 0x08, 0xB5, 0x16, 0x25,
+        0xC2, 0x16, 0x53, 0xDC, 0xB5, 0xF1, 0xDD, 0xCF, 0x28, 0xDD, 0x57, 0x90,
+        0x66, 0x33, 0x7B, 0x75, 0xF4, 0x8A, 0x19, 0xAC, 0x1F, 0x44, 0xC2, 0xF6,
+        0x21, 0x07, 0xE9, 0xCC, 0xDD, 0xCF, 0x4A, 0x34, 0xA1, 0x24, 0x82, 0xF8,
+        0xA1, 0x1D, 0x06, 0x90, 0x4B, 0x97, 0xB8, 0x10, 0xF2, 0x6A, 0x55, 0x30,
+        0xD9, 0x4F, 0x94, 0xE7, 0x7C, 0xBB, 0x73, 0xA3, 0x5F, 0xC6, 0xF1, 0xDB,
+        0x84, 0x3D, 0x29, 0x72, 0xD1, 0xAD, 0x2D, 0x77, 0x3F, 0x36, 0x24, 0x0F,
+        0xC4, 0x12, 0xD7, 0x3C, 0x65, 0x6C, 0xE1, 0x5A, 0x32, 0xAA, 0x0B, 0xA3,
+        0xA2, 0x72, 0x33, 0x00, 0x3C, 0x7E, 0x28, 0x36, 0x10, 0x90, 0x38, 0xFB};
+    EXPECT_EQ(response, compFirst512B);
+}
diff --git a/fw-update-legacy/test/inventory_manager_test.cpp b/fw-update-legacy/test/inventory_manager_test.cpp
new file mode 100644
index 0000000..024a0c0
--- /dev/null
+++ b/fw-update-legacy/test/inventory_manager_test.cpp
@@ -0,0 +1,187 @@
+#include "common/utils.hpp"
+#include "fw-update-legacy/inventory_manager.hpp"
+#include "requester/test/mock_request.hpp"
+#include "test/test_instance_id.hpp"
+
+#include <libpldm/firmware_update.h>
+
+#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()), instanceIdDb(),
+        reqHandler(nullptr, event, instanceIdDb, false, seconds(1), 2,
+                   milliseconds(100)),
+        inventoryManager(reqHandler, instanceIdDb, outDescriptorMap,
+                         outComponentInfoMap)
+    {}
+
+    int fd = -1;
+    sdeventplus::Event event;
+    TestInstanceIdDb instanceIdDb;
+    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-legacy/test/meson.build b/fw-update-legacy/test/meson.build
new file mode 100644
index 0000000..ee1bdb1
--- /dev/null
+++ b/fw-update-legacy/test/meson.build
@@ -0,0 +1,34 @@
+fw_update_test_src = declare_dependency(
+          sources: [
+            '../inventory_manager.cpp',
+            '../package_parser.cpp',
+            '../device_updater.cpp',
+            '../update_manager.cpp',
+            '../../common/utils.cpp',
+          ])
+
+tests = [
+  'inventory_manager_test',
+  'package_parser_test',
+  'device_updater_test'
+]
+
+foreach t : tests
+  test(t, executable(t.underscorify(), t + '.cpp',
+                     implicit_include_directories: false,
+                     include_directories: '../../pldmd',
+                     link_args: dynamic_linker,
+                     build_rpath: get_option('oe-sdk').enabled() ? rpath : '',
+                     dependencies: [
+                         fw_update_test_src,
+                         gmock,
+                         gtest,
+                         libpldm_dep,
+                         libpldmutils,
+                         nlohmann_json,
+                         phosphor_dbus_interfaces,
+                         phosphor_logging_dep,
+                         sdbusplus,
+                         sdeventplus]),
+       workdir: meson.current_source_dir())
+endforeach
diff --git a/fw-update-legacy/test/package_parser_test.cpp b/fw-update-legacy/test/package_parser_test.cpp
new file mode 100644
index 0000000..111b31e
--- /dev/null
+++ b/fw-update-legacy/test/package_parser_test.cpp
@@ -0,0 +1,183 @@
+#include "fw-update-legacy/package_parser.hpp"
+
+#include <typeinfo>
+
+#include <gmock/gmock.h>
+#include <gtest/gtest.h>
+
+using namespace pldm::fw_update;
+
+TEST(PackageParser, ValidPkgSingleDescriptorSingleComponent)
+{
+    std::vector<uint8_t> fwPkgHdr{
+        0xF0, 0x18, 0x87, 0x8C, 0xCB, 0x7D, 0x49, 0x43, 0x98, 0x00, 0xA0, 0x2F,
+        0x05, 0x9A, 0xCA, 0x02, 0x01, 0x8B, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+        0x00, 0x00, 0x00, 0x19, 0x0C, 0xE5, 0x07, 0x00, 0x08, 0x00, 0x01, 0x0E,
+        0x56, 0x65, 0x72, 0x73, 0x69, 0x6F, 0x6E, 0x53, 0x74, 0x72, 0x69, 0x6E,
+        0x67, 0x31, 0x01, 0x2E, 0x00, 0x01, 0x01, 0x00, 0x00, 0x00, 0x01, 0x0E,
+        0x00, 0x00, 0x01, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6F, 0x6E, 0x53, 0x74,
+        0x72, 0x69, 0x6E, 0x67, 0x32, 0x02, 0x00, 0x10, 0x00, 0x16, 0x20, 0x23,
+        0xC9, 0x3E, 0xC5, 0x41, 0x15, 0x95, 0xF4, 0x48, 0x70, 0x1D, 0x49, 0xD6,
+        0x75, 0x01, 0x00, 0x0A, 0x00, 0x64, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0x00,
+        0x00, 0x00, 0x00, 0x8B, 0x00, 0x00, 0x00, 0x1B, 0x00, 0x00, 0x00, 0x01,
+        0x0E, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6F, 0x6E, 0x53, 0x74, 0x72, 0x69,
+        0x6E, 0x67, 0x33, 0x4F, 0x96, 0xAE, 0x56};
+
+    constexpr uintmax_t pkgSize = 166;
+    constexpr std::string_view pkgVersion{"VersionString1"};
+    auto parser = parsePkgHeader(fwPkgHdr);
+    auto obj = parser.get();
+    EXPECT_EQ(typeid(*obj).name(), typeid(PackageParserV1).name());
+    EXPECT_EQ(parser->pkgHeaderSize, fwPkgHdr.size());
+    EXPECT_EQ(parser->pkgVersion, pkgVersion);
+
+    parser->parse(fwPkgHdr, pkgSize);
+    auto outfwDeviceIDRecords = parser->getFwDeviceIDRecords();
+    FirmwareDeviceIDRecords fwDeviceIDRecords{
+        {1,
+         {0},
+         "VersionString2",
+         {{PLDM_FWUP_UUID,
+           std::vector<uint8_t>{0x16, 0x20, 0x23, 0xC9, 0x3E, 0xC5, 0x41, 0x15,
+                                0x95, 0xF4, 0x48, 0x70, 0x1D, 0x49, 0xD6,
+                                0x75}}},
+         {}},
+    };
+    EXPECT_EQ(outfwDeviceIDRecords, fwDeviceIDRecords);
+
+    auto outCompImageInfos = parser->getComponentImageInfos();
+    ComponentImageInfos compImageInfos{
+        {10, 100, 0xFFFFFFFF, 0, 0, 139, 27, "VersionString3"}};
+    EXPECT_EQ(outCompImageInfos, compImageInfos);
+}
+
+TEST(PackageParser, ValidPkgMultipleDescriptorsMultipleComponents)
+{
+    std::vector<uint8_t> fwPkgHdr{
+        0xF0, 0x18, 0x87, 0x8C, 0xCB, 0x7D, 0x49, 0x43, 0x98, 0x00, 0xA0, 0x2F,
+        0x05, 0x9A, 0xCA, 0x02, 0x01, 0x46, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00,
+        0x00, 0x00, 0x00, 0x19, 0x0C, 0xE5, 0x07, 0x00, 0x08, 0x00, 0x01, 0x0E,
+        0x56, 0x65, 0x72, 0x73, 0x69, 0x6F, 0x6E, 0x53, 0x74, 0x72, 0x69, 0x6E,
+        0x67, 0x31, 0x03, 0x45, 0x00, 0x03, 0x01, 0x00, 0x00, 0x00, 0x01, 0x0E,
+        0x00, 0x00, 0x03, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6F, 0x6E, 0x53, 0x74,
+        0x72, 0x69, 0x6E, 0x67, 0x32, 0x02, 0x00, 0x10, 0x00, 0x12, 0x44, 0xD2,
+        0x64, 0x8D, 0x7D, 0x47, 0x18, 0xA0, 0x30, 0xFC, 0x8A, 0x56, 0x58, 0x7D,
+        0x5B, 0x01, 0x00, 0x04, 0x00, 0x47, 0x16, 0x00, 0x00, 0xFF, 0xFF, 0x0B,
+        0x00, 0x01, 0x07, 0x4F, 0x70, 0x65, 0x6E, 0x42, 0x4D, 0x43, 0x12, 0x34,
+        0x2E, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x01, 0x0E, 0x00, 0x00, 0x07,
+        0x56, 0x65, 0x72, 0x73, 0x69, 0x6F, 0x6E, 0x53, 0x74, 0x72, 0x69, 0x6E,
+        0x67, 0x33, 0x02, 0x00, 0x10, 0x00, 0x12, 0x44, 0xD2, 0x64, 0x8D, 0x7D,
+        0x47, 0x18, 0xA0, 0x30, 0xFC, 0x8A, 0x56, 0x58, 0x7D, 0x5C, 0x2E, 0x00,
+        0x01, 0x00, 0x00, 0x00, 0x00, 0x01, 0x0E, 0x00, 0x00, 0x01, 0x56, 0x65,
+        0x72, 0x73, 0x69, 0x6F, 0x6E, 0x53, 0x74, 0x72, 0x69, 0x6E, 0x67, 0x34,
+        0x02, 0x00, 0x10, 0x00, 0x12, 0x44, 0xD2, 0x64, 0x8D, 0x7D, 0x47, 0x18,
+        0xA0, 0x30, 0xFC, 0x8A, 0x56, 0x58, 0x7D, 0x5D, 0x03, 0x00, 0x0A, 0x00,
+        0x64, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x46, 0x01,
+        0x00, 0x00, 0x1B, 0x00, 0x00, 0x00, 0x01, 0x0E, 0x56, 0x65, 0x72, 0x73,
+        0x69, 0x6F, 0x6E, 0x53, 0x74, 0x72, 0x69, 0x6E, 0x67, 0x35, 0x0A, 0x00,
+        0xC8, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x01, 0x00, 0x61, 0x01,
+        0x00, 0x00, 0x1B, 0x00, 0x00, 0x00, 0x01, 0x0E, 0x56, 0x65, 0x72, 0x73,
+        0x69, 0x6F, 0x6E, 0x53, 0x74, 0x72, 0x69, 0x6E, 0x67, 0x36, 0x10, 0x00,
+        0x2C, 0x01, 0xFF, 0xFF, 0xFF, 0xFF, 0x01, 0x00, 0x0C, 0x00, 0x7C, 0x01,
+        0x00, 0x00, 0x1B, 0x00, 0x00, 0x00, 0x01, 0x0E, 0x56, 0x65, 0x72, 0x73,
+        0x69, 0x6F, 0x6E, 0x53, 0x74, 0x72, 0x69, 0x6E, 0x67, 0x37, 0xF1, 0x90,
+        0x9C, 0x71};
+
+    constexpr uintmax_t pkgSize = 407;
+    constexpr std::string_view pkgVersion{"VersionString1"};
+    auto parser = parsePkgHeader(fwPkgHdr);
+    auto obj = parser.get();
+    EXPECT_EQ(typeid(*obj).name(), typeid(PackageParserV1).name());
+    EXPECT_EQ(parser->pkgHeaderSize, fwPkgHdr.size());
+    EXPECT_EQ(parser->pkgVersion, pkgVersion);
+
+    parser->parse(fwPkgHdr, pkgSize);
+    auto outfwDeviceIDRecords = parser->getFwDeviceIDRecords();
+    FirmwareDeviceIDRecords fwDeviceIDRecords{
+        {1,
+         {0, 1},
+         "VersionString2",
+         {{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_IANA_ENTERPRISE_ID,
+           std::vector<uint8_t>{0x47, 0x16, 0x00, 0x00}},
+          {PLDM_FWUP_VENDOR_DEFINED,
+           std::make_tuple("OpenBMC", std::vector<uint8_t>{0x12, 0x34})}},
+         {}},
+        {0,
+         {0, 1, 2},
+         "VersionString3",
+         {{PLDM_FWUP_UUID,
+           std::vector<uint8_t>{0x12, 0x44, 0xD2, 0x64, 0x8D, 0x7D, 0x47, 0x18,
+                                0xA0, 0x30, 0xFC, 0x8A, 0x56, 0x58, 0x7D,
+                                0x5C}}},
+         {}},
+        {0,
+         {0},
+         "VersionString4",
+         {{PLDM_FWUP_UUID,
+           std::vector<uint8_t>{0x12, 0x44, 0xD2, 0x64, 0x8D, 0x7D, 0x47, 0x18,
+                                0xA0, 0x30, 0xFC, 0x8A, 0x56, 0x58, 0x7D,
+                                0x5D}}},
+         {}},
+    };
+    EXPECT_EQ(outfwDeviceIDRecords, fwDeviceIDRecords);
+
+    auto outCompImageInfos = parser->getComponentImageInfos();
+    ComponentImageInfos compImageInfos{
+        {10, 100, 0xFFFFFFFF, 0, 0, 326, 27, "VersionString5"},
+        {10, 200, 0xFFFFFFFF, 0, 1, 353, 27, "VersionString6"},
+        {16, 300, 0xFFFFFFFF, 1, 12, 380, 27, "VersionString7"}};
+    EXPECT_EQ(outCompImageInfos, compImageInfos);
+}
+
+TEST(PackageParser, InvalidPkgHeaderInfoIncomplete)
+{
+    std::vector<uint8_t> fwPkgHdr{0xF0, 0x18, 0x87, 0x8C, 0xCB, 0x7D,
+                                  0x49, 0x43, 0x98, 0x00, 0xA0, 0x2F,
+                                  0x05, 0x9A, 0xCA, 0x02};
+
+    auto parser = parsePkgHeader(fwPkgHdr);
+    EXPECT_EQ(parser, nullptr);
+}
+
+TEST(PackageParser, InvalidPkgNotSupportedHeaderFormat)
+{
+    std::vector<uint8_t> fwPkgHdr{
+        0x12, 0x44, 0xD2, 0x64, 0x8D, 0x7D, 0x47, 0x18, 0xA0, 0x30,
+        0xFC, 0x8A, 0x56, 0x58, 0x7D, 0x5B, 0x02, 0x8B, 0x00, 0x00,
+        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x19, 0x0C, 0xE5,
+        0x07, 0x00, 0x08, 0x00, 0x01, 0x0E, 0x56, 0x65, 0x72, 0x73,
+        0x69, 0x6F, 0x6E, 0x53, 0x74, 0x72, 0x69, 0x6E, 0x67, 0x31};
+
+    auto parser = parsePkgHeader(fwPkgHdr);
+    EXPECT_EQ(parser, nullptr);
+}
+
+TEST(PackageParser, InvalidPkgBadChecksum)
+{
+    std::vector<uint8_t> fwPkgHdr{
+        0xF0, 0x18, 0x87, 0x8C, 0xCB, 0x7D, 0x49, 0x43, 0x98, 0x00, 0xA0, 0x2F,
+        0x05, 0x9A, 0xCA, 0x02, 0x01, 0x8B, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+        0x00, 0x00, 0x00, 0x19, 0x0C, 0xE5, 0x07, 0x00, 0x08, 0x00, 0x01, 0x0E,
+        0x56, 0x65, 0x72, 0x73, 0x69, 0x6F, 0x6E, 0x53, 0x74, 0x72, 0x69, 0x6E,
+        0x67, 0x31, 0x01, 0x2E, 0x00, 0x01, 0x01, 0x00, 0x00, 0x00, 0x01, 0x0E,
+        0x00, 0x00, 0x01, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6F, 0x6E, 0x53, 0x74,
+        0x72, 0x69, 0x6E, 0x67, 0x32, 0x02, 0x00, 0x10, 0x00, 0x16, 0x20, 0x23,
+        0xC9, 0x3E, 0xC5, 0x41, 0x15, 0x95, 0xF4, 0x48, 0x70, 0x1D, 0x49, 0xD6,
+        0x75, 0x01, 0x00, 0x0A, 0x00, 0x64, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0x00,
+        0x00, 0x00, 0x00, 0x8B, 0x00, 0x00, 0x00, 0x1B, 0x00, 0x00, 0x00, 0x01,
+        0x0E, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6F, 0x6E, 0x53, 0x74, 0x72, 0x69,
+        0x6E, 0x67, 0x33, 0x4F, 0x96, 0xAE, 0x57};
+
+    constexpr uintmax_t pkgSize = 166;
+    constexpr std::string_view pkgVersion{"VersionString1"};
+    auto parser = parsePkgHeader(fwPkgHdr);
+    auto obj = parser.get();
+    EXPECT_EQ(typeid(*obj).name(), typeid(PackageParserV1).name());
+    EXPECT_EQ(parser->pkgHeaderSize, fwPkgHdr.size());
+    EXPECT_EQ(parser->pkgVersion, pkgVersion);
+    EXPECT_THROW(parser->parse(fwPkgHdr, pkgSize), std::exception);
+}
diff --git a/fw-update-legacy/test/test_pkg b/fw-update-legacy/test/test_pkg
new file mode 100644
index 0000000..ccf9568
--- /dev/null
+++ b/fw-update-legacy/test/test_pkg
Binary files differ
diff --git a/fw-update-legacy/update_manager.cpp b/fw-update-legacy/update_manager.cpp
new file mode 100644
index 0000000..6cdf3d9
--- /dev/null
+++ b/fw-update-legacy/update_manager.cpp
@@ -0,0 +1,347 @@
+#include "update_manager.hpp"
+
+#include "activation.hpp"
+#include "common/utils.hpp"
+#include "package_parser.hpp"
+
+#include <phosphor-logging/lg2.hpp>
+
+#include <cassert>
+#include <cmath>
+#include <filesystem>
+#include <fstream>
+#include <string>
+
+PHOSPHOR_LOG2_USING;
+
+namespace pldm
+{
+
+namespace fw_update
+{
+
+namespace fs = std::filesystem;
+namespace software = sdbusplus::xyz::openbmc_project::Software::server;
+
+int UpdateManager::processPackage(const std::filesystem::path& packageFilePath)
+{
+    // If no devices discovered, take no action on the package.
+    if (!descriptorMap.size())
+    {
+        return 0;
+    }
+
+    namespace software = sdbusplus::xyz::openbmc_project::Software::server;
+    // If a firmware activation of a package is in progress, don't proceed with
+    // package processing
+    if (activation)
+    {
+        if (activation->activation() ==
+            software::Activation::Activations::Activating)
+        {
+            error(
+                "Activation of PLDM FW update package already in progress, PACKAGE_VERSION={PKG_VERS}",
+                "PKG_VERS", parser->pkgVersion);
+            std::filesystem::remove(packageFilePath);
+            return -1;
+        }
+        else
+        {
+            clearActivationInfo();
+        }
+    }
+
+    package.open(packageFilePath,
+                 std::ios::binary | std::ios::in | std::ios::ate);
+    if (!package.good())
+    {
+        error(
+            "Opening the PLDM FW update package failed, ERR={ERR}, PACKAGEFILE={PKG_FILE}",
+            "ERR", unsigned(errno), "PKG_FILE", packageFilePath.c_str());
+        package.close();
+        std::filesystem::remove(packageFilePath);
+        return -1;
+    }
+
+    uintmax_t packageSize = package.tellg();
+    if (packageSize < sizeof(pldm_package_header_information))
+    {
+        error(
+            "PLDM FW update package length less than the length of the package header information, PACKAGESIZE={PKG_SIZE}",
+            "PKG_SIZE", packageSize);
+        package.close();
+        std::filesystem::remove(packageFilePath);
+        return -1;
+    }
+
+    package.seekg(0);
+    std::vector<uint8_t> packageHeader(sizeof(pldm_package_header_information));
+    package.read(reinterpret_cast<char*>(packageHeader.data()),
+                 sizeof(pldm_package_header_information));
+
+    auto pkgHeaderInfo =
+        reinterpret_cast<const pldm_package_header_information*>(
+            packageHeader.data());
+    auto pkgHeaderInfoSize = sizeof(pldm_package_header_information) +
+                             pkgHeaderInfo->package_version_string_length;
+    packageHeader.clear();
+    packageHeader.resize(pkgHeaderInfoSize);
+    package.seekg(0);
+    package.read(reinterpret_cast<char*>(packageHeader.data()),
+                 pkgHeaderInfoSize);
+
+    parser = parsePkgHeader(packageHeader);
+    if (parser == nullptr)
+    {
+        error("Invalid PLDM package header information");
+        package.close();
+        std::filesystem::remove(packageFilePath);
+        return -1;
+    }
+
+    // Populate object path with the hash of the package version
+    size_t versionHash = std::hash<std::string>{}(parser->pkgVersion);
+    objPath = swRootPath + std::to_string(versionHash);
+
+    package.seekg(0);
+    packageHeader.resize(parser->pkgHeaderSize);
+    package.read(reinterpret_cast<char*>(packageHeader.data()),
+                 parser->pkgHeaderSize);
+    try
+    {
+        parser->parse(packageHeader, packageSize);
+    }
+    catch (const std::exception& e)
+    {
+        error("Invalid PLDM package header");
+        activation = std::make_unique<Activation>(
+            pldm::utils::DBusHandler::getBus(), objPath,
+            software::Activation::Activations::Invalid, this);
+        package.close();
+        parser.reset();
+        return -1;
+    }
+
+    auto deviceUpdaterInfos =
+        associatePkgToDevices(parser->getFwDeviceIDRecords(), descriptorMap,
+                              totalNumComponentUpdates);
+    if (!deviceUpdaterInfos.size())
+    {
+        error(
+            "No matching devices found with the PLDM firmware update package");
+        activation = std::make_unique<Activation>(
+            pldm::utils::DBusHandler::getBus(), objPath,
+            software::Activation::Activations::Invalid, this);
+        package.close();
+        parser.reset();
+        return 0;
+    }
+    else
+    {
+        info("Found {COUNT} devices for firmware update:", "COUNT",
+             deviceUpdaterInfos.size());
+        for (const auto& [eid, _] : deviceUpdaterInfos)
+        {
+            info("   EID {EID}", "EID", eid);
+        }
+    }
+
+    const auto& fwDeviceIDRecords = parser->getFwDeviceIDRecords();
+    const auto& compImageInfos = parser->getComponentImageInfos();
+
+    for (const auto& deviceUpdaterInfo : deviceUpdaterInfos)
+    {
+        const auto& fwDeviceIDRecord =
+            fwDeviceIDRecords[deviceUpdaterInfo.second];
+        auto search = componentInfoMap.find(deviceUpdaterInfo.first);
+        deviceUpdaterMap.emplace(
+            deviceUpdaterInfo.first,
+            std::make_unique<DeviceUpdater>(
+                deviceUpdaterInfo.first, package, fwDeviceIDRecord,
+                compImageInfos, search->second, MAXIMUM_TRANSFER_SIZE, this));
+    }
+
+    fwPackageFilePath = packageFilePath;
+    activation = std::make_unique<Activation>(
+        pldm::utils::DBusHandler::getBus(), objPath,
+        software::Activation::Activations::Ready, this);
+    activationProgress = std::make_unique<ActivationProgress>(
+        pldm::utils::DBusHandler::getBus(), objPath);
+
+    return 0;
+}
+
+DeviceUpdaterInfos UpdateManager::associatePkgToDevices(
+    const FirmwareDeviceIDRecords& fwDeviceIDRecords,
+    const DescriptorMap& descriptorMap,
+    TotalComponentUpdates& totalNumComponentUpdates)
+{
+    info("Firmware update package descriptors:");
+    for (size_t index = 0; index < fwDeviceIDRecords.size(); ++index)
+    {
+        const auto& fw_update_descriptor =
+            std::get<Descriptors>(fwDeviceIDRecords[index]);
+        for (const auto& [descriptor_type, descriptor] : fw_update_descriptor)
+        {
+            if (std::holds_alternative<DescriptorData>(descriptor))
+            {
+                const auto desc = std::get<DescriptorData>(descriptor);
+                std::stringstream ss;
+                ss << std::hex << std::setfill('0');
+                for (const auto& byte : desc)
+                {
+                    ss << std::setw(2) << static_cast<int>(byte);
+                }
+                info("Index {IND} Descriptor type: {TYPE}, descriptor: {DESC}",
+                     "IND", index, "TYPE", descriptor_type, "DESC", ss.str());
+            }
+            else
+            {
+                const auto [desc_title, desc] =
+                    std::get<VendorDefinedDescriptorInfo>(descriptor);
+                std::stringstream ss;
+                ss << std::hex << std::setfill('0');
+                for (const auto& byte : desc)
+                {
+                    ss << std::setw(2) << static_cast<int>(byte);
+                }
+                info(
+                    "Index {IND} Descriptor type: {TYPE}, descriptor: {DESC_TITLE} {DESC}",
+                    "IND", index, "TYPE", descriptor_type, "DESC_TITLE",
+                    desc_title, "DESC", ss.str());
+            }
+        }
+    }
+    DeviceUpdaterInfos deviceUpdaterInfos;
+    for (size_t index = 0; index < fwDeviceIDRecords.size(); ++index)
+    {
+        const auto& deviceIDDescriptors =
+            std::get<Descriptors>(fwDeviceIDRecords[index]);
+        for (const auto& [eid, descriptors] : descriptorMap)
+        {
+            if (std::includes(descriptors.begin(), descriptors.end(),
+                              deviceIDDescriptors.begin(),
+                              deviceIDDescriptors.end()))
+            {
+                deviceUpdaterInfos.emplace_back(std::make_pair(eid, index));
+                const auto& applicableComponents =
+                    std::get<ApplicableComponents>(fwDeviceIDRecords[index]);
+                totalNumComponentUpdates += applicableComponents.size();
+                info(
+                    "Matched component for firmware update: EID {EID} and index {IND}",
+                    "EID", eid, "IND", index);
+            }
+        }
+    }
+    return deviceUpdaterInfos;
+}
+
+void UpdateManager::updateDeviceCompletion(mctp_eid_t eid, bool status)
+{
+    deviceUpdateCompletionMap.emplace(eid, status);
+    info("Completed device EID {EID} firmware update with status {STATUS}",
+         "EID", eid, "STATUS",
+         status ? std::string("SUCCESS") : std::string("FAILURE"));
+    if (deviceUpdateCompletionMap.size() == deviceUpdaterMap.size())
+    {
+        for (const auto& [eid, status] : deviceUpdateCompletionMap)
+        {
+            if (!status)
+            {
+                activation->activation(
+                    software::Activation::Activations::Failed);
+                return;
+            }
+        }
+
+        auto endTime = std::chrono::steady_clock::now();
+        auto dur =
+            std::chrono::duration<double, std::milli>(endTime - startTime)
+                .count();
+        error("Firmware update time: {DURATION}ms", "DURATION", dur);
+        activation->activation(software::Activation::Activations::Active);
+    }
+    return;
+}
+
+Response UpdateManager::handleRequest(mctp_eid_t eid, uint8_t command,
+                                      const pldm_msg* request, size_t reqMsgLen)
+{
+    Response response(sizeof(pldm_msg), 0);
+    if (deviceUpdaterMap.contains(eid))
+    {
+        auto search = deviceUpdaterMap.find(eid);
+        if (command == PLDM_REQUEST_FIRMWARE_DATA)
+        {
+            return search->second->requestFwData(request, reqMsgLen);
+        }
+        else if (command == PLDM_TRANSFER_COMPLETE)
+        {
+            return search->second->transferComplete(request, reqMsgLen);
+        }
+        else if (command == PLDM_VERIFY_COMPLETE)
+        {
+            return search->second->verifyComplete(request, reqMsgLen);
+        }
+        else if (command == PLDM_APPLY_COMPLETE)
+        {
+            return search->second->applyComplete(request, reqMsgLen);
+        }
+        else
+        {
+            auto ptr = reinterpret_cast<pldm_msg*>(response.data());
+            auto rc = encode_cc_only_resp(
+                request->hdr.instance_id, request->hdr.type,
+                request->hdr.command, PLDM_ERROR_INVALID_DATA, ptr);
+            assert(rc == PLDM_SUCCESS);
+        }
+    }
+    else
+    {
+        auto ptr = reinterpret_cast<pldm_msg*>(response.data());
+        auto rc = encode_cc_only_resp(request->hdr.instance_id,
+                                      request->hdr.type, +request->hdr.command,
+                                      PLDM_FWUP_COMMAND_NOT_EXPECTED, ptr);
+        assert(rc == PLDM_SUCCESS);
+    }
+
+    return response;
+}
+
+void UpdateManager::activatePackage()
+{
+    startTime = std::chrono::steady_clock::now();
+    info("Starting firmware update for {COUNT} devices.", "COUNT",
+         deviceUpdaterMap.size());
+    for (const auto& [eid, deviceUpdaterPtr] : deviceUpdaterMap)
+    {
+        deviceUpdaterPtr->startFwUpdateFlow();
+    }
+}
+
+void UpdateManager::clearActivationInfo()
+{
+    activation.reset();
+    activationProgress.reset();
+    objPath.clear();
+
+    deviceUpdaterMap.clear();
+    deviceUpdateCompletionMap.clear();
+    parser.reset();
+    package.close();
+    std::filesystem::remove(fwPackageFilePath);
+    totalNumComponentUpdates = 0;
+    compUpdateCompletedCount = 0;
+}
+
+void UpdateManager::updateActivationProgress()
+{
+    compUpdateCompletedCount++;
+    auto progressPercent = static_cast<uint8_t>(std::floor(
+        (100 * compUpdateCompletedCount) / totalNumComponentUpdates));
+    activationProgress->progress(progressPercent);
+}
+
+} // namespace fw_update
+
+} // namespace pldm
diff --git a/fw-update-legacy/update_manager.hpp b/fw-update-legacy/update_manager.hpp
new file mode 100644
index 0000000..b89cbcc
--- /dev/null
+++ b/fw-update-legacy/update_manager.hpp
@@ -0,0 +1,134 @@
+#pragma once
+
+#include "common/instance_id.hpp"
+#include "common/types.hpp"
+#include "device_updater.hpp"
+#include "package_parser.hpp"
+#include "requester/handler.hpp"
+#include "watch.hpp"
+
+#include <libpldm/base.h>
+
+#include <chrono>
+#include <filesystem>
+#include <fstream>
+#include <tuple>
+#include <unordered_map>
+
+namespace pldm
+{
+
+namespace fw_update
+{
+
+using namespace sdeventplus;
+using namespace sdeventplus::source;
+using namespace pldm;
+
+using DeviceIDRecordOffset = size_t;
+using DeviceUpdaterInfo = std::pair<mctp_eid_t, DeviceIDRecordOffset>;
+using DeviceUpdaterInfos = std::vector<DeviceUpdaterInfo>;
+using TotalComponentUpdates = size_t;
+
+class Activation;
+class ActivationProgress;
+
+class UpdateManager
+{
+  public:
+    UpdateManager() = delete;
+    UpdateManager(const UpdateManager&) = delete;
+    UpdateManager(UpdateManager&&) = delete;
+    UpdateManager& operator=(const UpdateManager&) = delete;
+    UpdateManager& operator=(UpdateManager&&) = delete;
+    ~UpdateManager() = default;
+
+    explicit UpdateManager(
+        Event& event,
+        pldm::requester::Handler<pldm::requester::Request>& handler,
+        InstanceIdDb& instanceIdDb, const DescriptorMap& descriptorMap,
+        const ComponentInfoMap& componentInfoMap) :
+        event(event), handler(handler), instanceIdDb(instanceIdDb),
+        descriptorMap(descriptorMap), componentInfoMap(componentInfoMap),
+        watch(event.get(),
+              std::bind_front(&UpdateManager::processPackage, this)),
+        totalNumComponentUpdates(0), compUpdateCompletedCount(0)
+    {}
+
+    /** @brief Handle PLDM request for the commands in the FW update
+     *         specification
+     *
+     *  @param[in] eid - Remote MCTP Endpoint ID
+     *  @param[in] command - PLDM command code
+     *  @param[in] request - PLDM request message
+     *  @param[in] requestLen - PLDM request message length
+     *
+     *  @return PLDM response message
+     */
+    Response handleRequest(mctp_eid_t eid, uint8_t command,
+                           const pldm_msg* request, size_t reqMsgLen);
+
+    int processPackage(const std::filesystem::path& packageFilePath);
+
+    void updateDeviceCompletion(mctp_eid_t eid, bool status);
+
+    void updateActivationProgress();
+
+    /** @brief Callback function that will be invoked when the
+     *         RequestedActivation will be set to active in the Activation
+     *         interface
+     */
+    void activatePackage();
+
+    void clearActivationInfo();
+
+    /** @brief
+     *
+     */
+    DeviceUpdaterInfos associatePkgToDevices(
+        const FirmwareDeviceIDRecords& fwDeviceIDRecords,
+        const DescriptorMap& descriptorMap,
+        TotalComponentUpdates& totalNumComponentUpdates);
+
+    const std::string swRootPath{"/xyz/openbmc_project/software/"};
+    Event& event; //!< reference to PLDM daemon's main event loop
+    /** @brief PLDM request handler */
+    pldm::requester::Handler<pldm::requester::Request>& handler;
+    InstanceIdDb& instanceIdDb; //!< reference to an InstanceIdDb
+
+  private:
+    /** @brief Device identifiers of the managed FDs */
+    const DescriptorMap& descriptorMap;
+    /** @brief Component information needed for the update of the managed FDs */
+    const ComponentInfoMap& componentInfoMap;
+    Watch watch;
+
+    std::unique_ptr<Activation> activation;
+    std::unique_ptr<ActivationProgress> activationProgress;
+    std::string objPath;
+
+    std::filesystem::path fwPackageFilePath;
+    std::unique_ptr<PackageParser> parser;
+    std::ifstream package;
+
+    std::unordered_map<mctp_eid_t, std::unique_ptr<DeviceUpdater>>
+        deviceUpdaterMap;
+    std::unordered_map<mctp_eid_t, bool> deviceUpdateCompletionMap;
+
+    /** @brief Total number of component updates to calculate the progress of
+     *         the Firmware activation
+     */
+    size_t totalNumComponentUpdates;
+
+    /** @brief FW update package can contain updates for multiple firmware
+     *         devices and each device can have multiple components. Once
+     *         each component is updated (Transfer completed, Verified and
+     *         Applied) ActivationProgress is updated.
+     */
+    size_t compUpdateCompletedCount;
+    decltype(std::chrono::steady_clock::now()) startTime;
+};
+
+} // namespace fw_update
+
+} // namespace pldm
diff --git a/fw-update-legacy/watch.cpp b/fw-update-legacy/watch.cpp
new file mode 100644
index 0000000..4ad6bc5
--- /dev/null
+++ b/fw-update-legacy/watch.cpp
@@ -0,0 +1,146 @@
+#include "watch.hpp"
+
+#include <sys/inotify.h>
+#include <unistd.h>
+
+#include <phosphor-logging/lg2.hpp>
+
+#include <cstddef>
+#include <cstring>
+#include <filesystem>
+#include <stdexcept>
+#include <string>
+
+PHOSPHOR_LOG2_USING;
+
+namespace pldm
+{
+
+namespace fw_update
+{
+
+using namespace std::string_literals;
+namespace fs = std::filesystem;
+
+Watch::Watch(sd_event* loop, std::function<int(std::string&)> imageCallback) :
+    imageCallback(imageCallback)
+{
+    fd = inotify_init1(IN_NONBLOCK);
+    if (fd == -1)
+    {
+        // Store a copy of errno, because the string creation below will
+        // invalidate errno due to one more system calls.
+        const auto ec = errno;
+        error("Error {EC} {ERR} when calling inotify_init1.", "EC", ec, "ERR",
+              std::strerror(ec));
+        throw std::runtime_error(
+            "inotify_init1 failed, errno="s + std::strerror(ec));
+    }
+
+    const auto rc = sd_event_add_io(loop, nullptr, fd, EPOLLIN, callback, this);
+    if (rc < 0)
+    {
+        error("Error {EC} calling sd_event_add_io.", "EC", rc);
+        throw std::runtime_error(
+            "failed to add to event loop, rc="s + std::strerror(-rc));
+    }
+
+    setupWatch();
+}
+
+void Watch::setupWatch()
+{
+    // Check if FIRMWARE_PACKAGE_STAGING_DIR exists and create it if not.
+    fs::path imgDirPath(FIRMWARE_PACKAGE_STAGING_DIR);
+    if (!fs::is_directory(imgDirPath))
+    {
+        fs::create_directories(imgDirPath);
+        info("Directory {DIR} wasn't found, so it was created.", "DIR",
+             std::string(FIRMWARE_PACKAGE_STAGING_DIR));
+    }
+
+    wd = inotify_add_watch(fd, FIRMWARE_PACKAGE_STAGING_DIR,
+                           IN_CLOSE_WRITE | IN_DELETE_SELF);
+    if (wd == -1)
+    {
+        const auto ec = errno;
+        close(fd);
+        error("Error {EC} {ERR} calling inotify_add_watch.", "EC", ec, "ERR",
+              std::strerror(ec));
+        throw std::runtime_error(
+            "inotify_add_watch failed, errno="s + std::strerror(ec));
+    }
+    info("Set up filesystem watch for {DIR}", "DIR",
+         std::string(FIRMWARE_PACKAGE_STAGING_DIR));
+}
+
+Watch::~Watch()
+{
+    if (-1 != fd)
+    {
+        if (-1 != wd)
+        {
+            inotify_rm_watch(fd, wd);
+        }
+        close(fd);
+    }
+}
+
+int Watch::callback(sd_event_source* /* s */, int fd, uint32_t revents,
+                    void* userdata)
+{
+    if (!(revents & EPOLLIN))
+    {
+        return 0;
+    }
+
+    constexpr auto maxBytes = 1024;
+    uint8_t buffer[maxBytes];
+    auto bytes = read(fd, buffer, maxBytes);
+    if (0 > bytes)
+    {
+        auto ec = errno;
+        error("Error {EC} {ERR} reading inotify event.", "EC", ec, "ERR",
+              std::strerror(ec));
+        throw std::runtime_error(
+            "failed to read inotify event, errno="s + std::strerror(ec));
+    }
+
+    auto offset = 0;
+    while (offset < bytes)
+    {
+        auto event = reinterpret_cast<inotify_event*>(&buffer[offset]);
+        if ((event->mask & IN_CLOSE_WRITE) && !(event->mask & IN_ISDIR))
+        {
+            info("Received IN_CLOSE_WRITE event on {DIR} for {FILE}", "DIR",
+                 std::string(FIRMWARE_PACKAGE_STAGING_DIR), "FILE",
+                 event->name);
+            auto tarballPath =
+                std::string{FIRMWARE_PACKAGE_STAGING_DIR} + '/' + event->name;
+            auto rc = static_cast<Watch*>(userdata)->imageCallback(tarballPath);
+            if (rc < 0)
+            {
+                error("Error ({EC}) processing image {IMAGE_PATH}", "EC", rc,
+                      "IMAGE_PATH", tarballPath.c_str());
+            }
+        }
+        else if (event->mask & IN_DELETE_SELF)
+        {
+            info("Received IN_DELETE_SELF event on {DIR}", "DIR",
+                 std::string(FIRMWARE_PACKAGE_STAGING_DIR));
+            auto* const watch = reinterpret_cast<Watch*>(userdata);
+            watch->wd = -1;
+            // IN_DELETE_SELF event automatically cleans up the filesystem
+            // watch, so we can just go ahead and set it up again without
+            // cleaning.
+            watch->setupWatch();
+        }
+
+        offset += offsetof(inotify_event, name) + event->len;
+    }
+
+    return 0;
+}
+
+} // namespace fw_update
+} // namespace pldm
diff --git a/fw-update-legacy/watch.hpp b/fw-update-legacy/watch.hpp
new file mode 100644
index 0000000..be6c0e5
--- /dev/null
+++ b/fw-update-legacy/watch.hpp
@@ -0,0 +1,71 @@
+#pragma once
+
+#include <systemd/sd-event.h>
+
+#include <functional>
+#include <string>
+
+namespace pldm
+{
+
+namespace fw_update
+{
+
+/** @class Watch
+ *
+ *  @brief Adds inotify watch on software image upload directory
+ *
+ *  The inotify watch is hooked up with sd-event, so that on call back,
+ *  appropriate actions related to a software image upload can be taken.
+ */
+class Watch
+{
+  public:
+    /** @brief ctor - hook inotify watch with sd-event
+     *
+     *  @param[in] loop - sd-event object
+     *  @param[in] imageCallback - The callback function for processing
+     *                             the image
+     */
+    Watch(sd_event* loop, std::function<int(std::string&)> imageCallback);
+
+    Watch(const Watch&) = delete;
+    Watch& operator=(const Watch&) = delete;
+    Watch(Watch&&) = delete;
+    Watch& operator=(Watch&&) = delete;
+
+    /** @brief dtor - remove inotify watch and close fd's
+     */
+    ~Watch();
+
+  private:
+    /** @brief Sets up the filesystem watch to monitor
+     *  FIRMWARE_PACKAGE_STAGING_DIR for file creations, when imageCallback is
+     *  invoked, and deletion of the dir itself, so that the watch can be
+     *  recreated.
+     */
+    void setupWatch();
+
+    /** @brief sd-event callback
+     *
+     *  @param[in] s - event source, floating (unused) in our case
+     *  @param[in] fd - inotify fd
+     *  @param[in] revents - events that matched for fd
+     *  @param[in] userdata - pointer to Watch object
+     *  @returns 0 on success, -1 on fail
+     */
+    static int callback(sd_event_source* s, int fd, uint32_t revents,
+                        void* userdata);
+
+    /** @brief image upload directory watch descriptor */
+    int wd = -1;
+
+    /** @brief inotify file descriptor */
+    int fd = -1;
+
+    /** @brief The callback function for processing the image. */
+    std::function<int(std::string&)> imageCallback;
+};
+
+} // namespace fw_update
+} // namespace pldm
diff --git a/meson.build b/meson.build
index afd02cf..a5c83ec 100644
--- a/meson.build
+++ b/meson.build
@@ -147,6 +147,8 @@
 conf_data.set('SENSOR_POLLING_TIME', get_option('sensor-polling-time'))
 conf_data.set('UPDATE_TIMEOUT_SECONDS', get_option('update-timeout-seconds'))
 
+conf_data.set_quoted('FIRMWARE_PACKAGE_STAGING_DIR', get_option('firmware-package-staging-dir'))
+
 # Firmware update inotify option
 if get_option('fw-update-pkg-inotify').allowed()
     conf_data.set('FW_UPDATE_INOTIFY_ENABLED', 1)
@@ -304,11 +306,20 @@
 
 fw_update_sources = []
 
+fw_update_legacy_sources = [
+    'fw-update-legacy/inventory_manager.cpp',
+    'fw-update-legacy/package_parser.cpp',
+    'fw-update-legacy/device_updater.cpp',
+    'fw-update-legacy/watch.cpp',
+    'fw-update-legacy/update_manager.cpp',
+]
+
 executable(
     'pldmd',
     'pldmd/pldmd.cpp',
     'pldmd/dbus_impl_pdr.cpp',
     fw_update_sources,
+    fw_update_legacy_sources,
     'platform-mc/dbus_impl_fru.cpp',
     'platform-mc/terminus_manager.cpp',
     'platform-mc/terminus.cpp',
diff --git a/meson.options b/meson.options
index fe7d272..8d90a00 100644
--- a/meson.options
+++ b/meson.options
@@ -249,6 +249,13 @@
 )
 # Firmware Update configuration parameters
 option(
+    'firmware-package-staging-dir',
+    type: 'string',
+    value: '/tmp/pldm-images',
+    description: 'Firmware package staging directory for PLDM packages.',
+)
+
+option(
     'fw-update-pkg-inotify',
     type: 'feature',
     value: 'disabled',
diff --git a/pldmd/pldmd.cpp b/pldmd/pldmd.cpp
index e6054cc..a32f6dc 100644
--- a/pldmd/pldmd.cpp
+++ b/pldmd/pldmd.cpp
@@ -3,6 +3,7 @@
 #include "common/instance_id.hpp"
 #include "common/transport.hpp"
 #include "common/utils.hpp"
+#include "fw-update-legacy/manager.hpp"
 #include "invoker.hpp"
 #include "platform-mc/dbus_to_terminus_effecters.hpp"
 #include "platform-mc/manager.hpp"
@@ -113,7 +114,8 @@
 
 static std::optional<Response> processRxMsg(
     const std::vector<uint8_t>& requestMsg, Invoker& invoker,
-    requester::Handler<requester::Request>& handler, pldm_tid_t tid)
+    requester::Handler<requester::Request>& handler,
+    fw_update::Manager* fwManager, pldm_tid_t tid)
 {
     uint8_t eid = tid;
 
@@ -132,8 +134,17 @@
         size_t requestLen = requestMsg.size() - sizeof(struct pldm_msg_hdr);
         try
         {
-            response = invoker.handle(tid, hdrFields.pldm_type,
-                                      hdrFields.command, request, requestLen);
+            if (hdrFields.pldm_type != PLDM_FWUP)
+            {
+                response =
+                    invoker.handle(tid, hdrFields.pldm_type, hdrFields.command,
+                                   request, requestLen);
+            }
+            else
+            {
+                response = fwManager->handleRequest(eid, hdrFields.command,
+                                                    request, requestLen);
+            }
         }
         catch (const std::out_of_range& e)
         {
@@ -213,6 +224,8 @@
 
     std::unique_ptr<platform_mc::Manager> platformManager =
         std::make_unique<platform_mc::Manager>(event, reqHandler, instanceIdDb);
+    std::unique_ptr<fw_update::Manager> fwManager =
+        std::make_unique<fw_update::Manager>(event, reqHandler, instanceIdDb);
 
     pldm::host_effecters::HostEffecterParser hostEffecterParser(
         &instanceIdDb, pldmTransport.getEventSource(), pdrRepo.get(),
@@ -362,9 +375,9 @@
 #else
     MctpDiscovery mctpDiscoveryHandler(
         bus, std::initializer_list<MctpDiscoveryHandlerIntf*>{
-                 platformManager.get()});
+                 platformManager.get(), fwManager.get()});
 #endif
-    auto callback = [verbose, &invoker, &reqHandler, &pldmTransport,
+    auto callback = [verbose, &invoker, &reqHandler, &fwManager, &pldmTransport,
                      TID](IO& io, int fd, uint32_t revents) mutable {
         if (revents & (POLLHUP | POLLERR))
         {
@@ -398,8 +411,8 @@
                 printBuffer(Rx, requestMsgVec);
             }
             // process message and send response
-            auto response =
-                processRxMsg(requestMsgVec, invoker, reqHandler, TID);
+            auto response = processRxMsg(requestMsgVec, invoker, reqHandler,
+                                         fwManager.get(), TID);
             if (response.has_value())
             {
                 FlightRecorder::GetInstance().saveRecord(*response, true);