fw-update: Implement firmware UpdateManager

The UpdateManager parses the PLDM package and co-ordinates with
the DeviceUpdater to update all the PLDM enabled firmware devices.

Tested: Completed firmware update using PLDM for an FD

Signed-off-by: Tom Joseph <rushtotom@gmail.com>
Change-Id: Ia87675e0a88cb1f72ad82934e539739db193b9f6
diff --git a/fw-update/update_manager.cpp b/fw-update/update_manager.cpp
new file mode 100644
index 0000000..42e2156
--- /dev/null
+++ b/fw-update/update_manager.cpp
@@ -0,0 +1,296 @@
+#include "update_manager.hpp"
+
+#include "activation.hpp"
+#include "common/utils.hpp"
+#include "package_parser.hpp"
+
+#include <cassert>
+#include <cmath>
+#include <filesystem>
+#include <fstream>
+#include <string>
+
+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)
+        {
+            std::cerr
+                << "Activation of PLDM FW update package already in progress"
+                << ", PACKAGE_VERSION=" << parser->pkgVersion << "\n";
+            std::filesystem::remove(packageFilePath);
+            return -1;
+        }
+        else
+        {
+            clearActivationInfo();
+        }
+    }
+
+    package.open(packageFilePath,
+                 std::ios::binary | std::ios::in | std::ios::ate);
+    if (!package.good())
+    {
+        std::cerr << "Opening the PLDM FW update package failed, ERR="
+                  << unsigned(errno) << ", PACKAGEFILE=" << packageFilePath
+                  << "\n";
+        package.close();
+        std::filesystem::remove(packageFilePath);
+        return -1;
+    }
+
+    uintmax_t packageSize = package.tellg();
+    if (packageSize < sizeof(pldm_package_header_information))
+    {
+        std::cerr << "PLDM FW update package length less than the length of "
+                     "the package header information, PACKAGESIZE="
+                  << packageSize << "\n";
+        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)
+    {
+        std::cerr << "Invalid PLDM package header information"
+                  << "\n";
+        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)
+    {
+        std::cerr << "Invalid PLDM package header"
+                  << "\n";
+        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())
+    {
+        std::cerr
+            << "No matching devices found with the PLDM firmware update package"
+            << "\n";
+        activation = std::make_unique<Activation>(
+            pldm::utils::DBusHandler::getBus(), objPath,
+            software::Activation::Activations::Invalid, this);
+        package.close();
+        parser.reset();
+        return 0;
+    }
+
+    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, event, requester,
+                                     handler, 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)
+{
+    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();
+            }
+        }
+    }
+    return deviceUpdaterInfos;
+}
+
+void UpdateManager::updateDeviceCompletion(mctp_eid_t eid, bool status)
+{
+    deviceUpdateCompletionMap.emplace(eid, status);
+    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();
+        std::cerr << "Firmware update time: "
+                  << std::chrono::duration<double, std::milli>(endTime -
+                                                               startTime)
+                         .count()
+                  << " ms\n";
+        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();
+    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
\ No newline at end of file