support reading System Firmware Version

This commit implements reading System Firmware Version as below steps:
- Find all object path that include xyz.openbmc_project.Software.Version
interface.
- Get the Purpose property of above object paths.
- If the Purpose is Host then get the Version property.

Tested:
    1. Read system firmware version
       $ipmitool mc getsysinfo system_fw_version
    2. The version of firmware is shown

Change-Id: I84e25572253ddc7c2a48da9f6046920e55c8ccd4
Signed-off-by: Huy Le <hule@amperecomputing.com>
Signed-off-by: Thang Tran <thuutran@amperecomputing.com>
diff --git a/apphandler.cpp b/apphandler.cpp
index 0919cfb..9bad7ff 100644
--- a/apphandler.cpp
+++ b/apphandler.cpp
@@ -48,6 +48,7 @@
 
 constexpr auto bmc_state_interface = "xyz.openbmc_project.State.BMC";
 constexpr auto bmc_state_property = "CurrentBMCState";
+constexpr auto versionPurposeHostEnd = ".Host";
 
 static constexpr auto redundancyIntf =
     "xyz.openbmc_project.Software.RedundancyPriority";
@@ -1292,6 +1293,58 @@
     return ipmi::responseInvalidFieldRequest();
 }
 
+std::optional<std::string> getSysFWVersion(ipmi::Context::ptr& ctx)
+{
+    /*
+     * The System Firmware version is detected via following steps:
+     * - Get all of object paths that include
+     * "xyz.openbmc_project.Software.Version" interface.
+     * - Get the Purpose property of above object paths.
+     * - If the Purpose is Host then get the Version property.
+     */
+    ipmi::ObjectTree objectTree;
+    boost::system::error_code ec =
+        ipmi::getAllDbusObjects(ctx, softwareRoot, versionIntf, objectTree);
+    if (ec.value())
+    {
+        return std::nullopt;
+    }
+
+    for (const auto& [objPath, serviceMap] : objectTree)
+    {
+        for (const auto& [service, intfs] : serviceMap)
+        {
+            ipmi::PropertyMap props;
+            ec = ipmi::getAllDbusProperties(ctx, service, objPath, versionIntf,
+                                            props);
+            if (ec.value())
+            {
+                continue;
+            }
+
+            std::string purposeProp = std::string(
+                ipmi::mappedVariant<std::string>(props, "Purpose", ""));
+
+            if (!purposeProp.ends_with(versionPurposeHostEnd))
+            {
+                continue;
+            }
+
+            std::string sysFWVersion = std::string(
+                ipmi::mappedVariant<std::string>(props, "Version", ""));
+
+            if (sysFWVersion.empty())
+            {
+                return std::nullopt;
+            }
+
+            return sysFWVersion;
+        }
+    }
+
+    return std::nullopt;
+}
+
 static std::unique_ptr<SysInfoParamStore> sysInfoParamStore;
 
 static std::string sysInfoReadSystemName()
@@ -1337,9 +1390,9 @@
 ipmi::RspType<uint8_t,                // Parameter revision
               std::optional<uint8_t>, // data1 / setSelector / ProgressStatus
               std::optional<std::vector<uint8_t>>> // data2-17
-    ipmiAppGetSystemInfo(uint7_t reserved, bool getRevision,
-                         uint8_t paramSelector, uint8_t setSelector,
-                         uint8_t BlockSelector)
+    ipmiAppGetSystemInfo(ipmi::Context::ptr ctx, uint7_t reserved,
+                         bool getRevision, uint8_t paramSelector,
+                         uint8_t setSelector, uint8_t BlockSelector)
 {
     if (reserved || (paramSelector >= invalidParamSelectorStart &&
                      paramSelector <= invalidParamSelectorEnd))
@@ -1373,6 +1426,17 @@
                                   sysInfoReadSystemName);
     }
 
+    if (paramSelector == IPMI_SYSINFO_SYSTEM_FW_VERSION)
+    {
+        auto fwVersion = getSysFWVersion(ctx);
+
+        if (fwVersion == std::nullopt)
+        {
+            return ipmi::responseUnspecifiedError();
+        }
+        sysInfoParamStore->update(IPMI_SYSINFO_SYSTEM_FW_VERSION, *fwVersion);
+    }
+
     // Parameters other than Set In Progress are assumed to be strings.
     std::tuple<bool, std::string> ret =
         sysInfoParamStore->lookup(paramSelector);