Add support for Inventory.Decorator.Compatible

This adds support for the compatibility strings interface described in
https://github.com/openbmc/phosphor-dbus-interfaces/tree/master/yaml/xyz/openbmc_project/Software#compatibility.

The Version objects will now be created with the Inventory.Decorator.Compatible
interface with the compatibility names coming from the MANIFEST file.
e.g.
version=1.2.3
MachineName=foo
purpose=Other
ExtendedVersion=a.b.c
CompatibleName=foo.bar
CompatibleName=baz.bim

Tested:
$ busctl get-property xyz.openbmc_project.Software.Version \
    /xyz/openbmc_project/software/517751da \
    xyz.openbmc_project.Inventory.Decorator.Compatible Names
as 2 "foo.bar" "baz.bim"

Signed-off-by: Justin Ledford <justinledford@google.com>
Change-Id: I9ee36af2d3d1494d533a3b09c466a250c4fe786b
diff --git a/image_manager.cpp b/image_manager.cpp
index 604b952..b02abd3 100644
--- a/image_manager.cpp
+++ b/image_manager.cpp
@@ -188,6 +188,10 @@
     std::string extendedVersion =
         Version::getValue(manifestPath.string(), "ExtendedVersion");
 
+    // Get CompatibleNames
+    std::vector<std::string> compatibleNames =
+        Version::getRepeatedValues(manifestPath.string(), "CompatibleName");
+
     // Compute id
     auto salt = std::to_string(randomGen());
     auto id = Version::getId(version + salt);
@@ -213,7 +217,7 @@
         // Create Version object
         auto versionPtr = std::make_unique<Version>(
             bus, objPath, version, purpose, extendedVersion,
-            imageDirPath.string(),
+            imageDirPath.string(), compatibleNames,
             std::bind(&Manager::erase, this, std::placeholders::_1), id);
         versionPtr->deleteObject =
             std::make_unique<phosphor::software::manager::Delete>(bus, objPath,
diff --git a/item_updater.cpp b/item_updater.cpp
index cfe0ae5..c8fda76 100644
--- a/item_updater.cpp
+++ b/item_updater.cpp
@@ -49,11 +49,14 @@
     auto purpose = VersionPurpose::Unknown;
     std::string extendedVersion;
     std::string version;
-    std::map<std::string, std::map<std::string, std::variant<std::string>>>
+    std::map<std::string,
+             std::map<std::string,
+                      std::variant<std::string, std::vector<std::string>>>>
         interfaces;
     msg.read(objPath, interfaces);
     std::string path(std::move(objPath));
     std::string filePath;
+    std::vector<std::string> compatibleNames;
 
     for (const auto& intf : interfaces)
     {
@@ -100,6 +103,17 @@
                 }
             }
         }
+        else if (intf.first == COMPATIBLE_IFACE)
+        {
+            for (const auto& property : intf.second)
+            {
+                if (property.first == "Names")
+                {
+                    compatibleNames =
+                        std::get<std::vector<std::string>>(property.second);
+                }
+            }
+        }
     }
     if (version.empty() || filePath.empty() ||
         purpose == VersionPurpose::Unknown)
@@ -140,6 +154,7 @@
 
         auto versionPtr = std::make_unique<VersionClass>(
             bus, path, version, purpose, extendedVersion, filePath,
+            compatibleNames,
             std::bind(&ItemUpdater::erase, this, std::placeholders::_1),
             versionId);
         versionPtr->deleteObject =
@@ -284,6 +299,7 @@
             // Create Version instance for this version.
             auto versionPtr = std::make_unique<VersionClass>(
                 bus, path, version, purpose, extendedVersion, flashId,
+                std::vector<std::string>(),
                 std::bind(&ItemUpdater::erase, this, std::placeholders::_1),
                 id);
             if (functional)
@@ -843,6 +859,7 @@
     };
     biosVersion = std::make_unique<VersionClass>(
         bus, path, version, VersionPurpose::Host, "", "",
+        std::vector<std::string>(),
         std::bind(dummyErase, std::placeholders::_1), "");
     biosVersion->deleteObject =
         std::make_unique<phosphor::software::manager::Delete>(bus, path,
diff --git a/meson.build b/meson.build
index c3ed12c..68954bc 100644
--- a/meson.build
+++ b/meson.build
@@ -40,6 +40,7 @@
 conf.set_quoted('VERSION_BUSNAME', 'xyz.openbmc_project.Software.Version')
 conf.set_quoted('VERSION_IFACE', 'xyz.openbmc_project.Software.Version')
 conf.set_quoted('EXTENDED_VERSION_IFACE', 'xyz.openbmc_project.Software.ExtendedVersion')
+conf.set_quoted('COMPATIBLE_IFACE', 'xyz.openbmc_project.Inventory.Decorator.Compatible')
 
 # Names of the forward and reverse associations
 conf.set_quoted('ACTIVATION_FWD_ASSOCIATION', 'inventory')
diff --git a/test/utest.cpp b/test/utest.cpp
index 1c32b1a..03dbe26 100644
--- a/test/utest.cpp
+++ b/test/utest.cpp
@@ -58,6 +58,25 @@
     EXPECT_EQ(Version::getValue(manifestFilePath, "purpose"), purpose);
 }
 
+TEST_F(VersionTest, TestGetRepeatedValue)
+{
+    auto manifestFilePath = _directory + "/" + "MANIFEST";
+    const std::vector<std::string> names = {"foo.bar", "baz.bim"};
+
+    std::ofstream file;
+    file.open(manifestFilePath, std::ofstream::out);
+    ASSERT_TRUE(file.is_open());
+
+    for (const auto& name : names)
+    {
+        file << "CompatibleName=" << name << "\n";
+    }
+    file.close();
+
+    EXPECT_EQ(Version::getRepeatedValues(manifestFilePath, "CompatibleName"),
+              names);
+}
+
 TEST_F(VersionTest, TestGetValueWithCRLF)
 {
     auto manifestFilePath = _directory + "/" + "MANIFEST";
diff --git a/version.cpp b/version.cpp
index 250c221..f7fbd2b 100644
--- a/version.cpp
+++ b/version.cpp
@@ -30,6 +30,23 @@
 std::string Version::getValue(const std::string& manifestFilePath,
                               std::string key)
 {
+    std::vector<std::string> values = getRepeatedValues(manifestFilePath, key);
+    if (values.empty())
+    {
+        return std::string{};
+    }
+    if (values.size() > 1)
+    {
+        error("Multiple values found in MANIFEST file for key: {KEY}", "KEY",
+              key);
+    }
+    return values.at(0);
+}
+
+std::vector<std::string>
+    Version::getRepeatedValues(const std::string& manifestFilePath,
+                               std::string key)
+{
     key = key + "=";
     auto keySize = key.length();
 
@@ -41,11 +58,10 @@
             Argument::ARGUMENT_VALUE(manifestFilePath.c_str()));
     }
 
-    std::string value{};
+    std::vector<std::string> values{};
     std::ifstream efile;
     std::string line;
-    efile.exceptions(std::ifstream::failbit | std::ifstream::badbit |
-                     std::ifstream::eofbit);
+    efile.exceptions(std::ifstream::failbit | std::ifstream::badbit);
 
     // Too many GCC bugs (53984, 66145) to do this the right way...
     try
@@ -61,19 +77,26 @@
             }
             if (line.compare(0, keySize, key) == 0)
             {
-                value = line.substr(keySize);
-                break;
+                values.push_back(line.substr(keySize));
             }
         }
         efile.close();
     }
     catch (const std::exception& e)
     {
-        error("Error occurred when reading MANIFEST file: {ERROR}", "KEY", key,
-              "ERROR", e);
+        if (!efile.eof())
+        {
+            error("Error occurred when reading MANIFEST file: {ERROR}", "KEY",
+                  key, "ERROR", e);
+        }
     }
 
-    return value;
+    if (values.empty())
+    {
+        error("No values found in MANIFEST file for key: {KEY}", "KEY", key);
+    }
+
+    return values;
 }
 
 using EVP_MD_CTX_Ptr =
diff --git a/version.hpp b/version.hpp
index b6269e0..b6f9172 100644
--- a/version.hpp
+++ b/version.hpp
@@ -1,6 +1,7 @@
 #pragma once
 
 #include "xyz/openbmc_project/Common/FilePath/server.hpp"
+#include "xyz/openbmc_project/Inventory/Decorator/Compatible/server.hpp"
 #include "xyz/openbmc_project/Object/Delete/server.hpp"
 #include "xyz/openbmc_project/Software/ExtendedVersion/server.hpp"
 #include "xyz/openbmc_project/Software/Version/server.hpp"
@@ -9,6 +10,7 @@
 
 #include <functional>
 #include <string>
+#include <vector>
 
 namespace phosphor
 {
@@ -22,7 +24,8 @@
 using VersionInherit = sdbusplus::server::object::object<
     sdbusplus::xyz::openbmc_project::Software::server::ExtendedVersion,
     sdbusplus::xyz::openbmc_project::Software::server::Version,
-    sdbusplus::xyz::openbmc_project::Common::server::FilePath>;
+    sdbusplus::xyz::openbmc_project::Common::server::FilePath,
+    sdbusplus::xyz::openbmc_project::Inventory::Decorator::server::Compatible>;
 using DeleteInherit = sdbusplus::server::object::object<
     sdbusplus::xyz::openbmc_project::Object::server::Delete>;
 
@@ -68,18 +71,20 @@
   public:
     /** @brief Constructs Version Software Manager
      *
-     * @param[in] bus            - The D-Bus bus object
-     * @param[in] objPath        - The D-Bus object path
-     * @param[in] versionString  - The version string
-     * @param[in] versionPurpose - The version purpose
-     * @param[in] extVersion     - The extended version
-     * @param[in] filePath       - The image filesystem path
-     * @param[in] callback       - The eraseFunc callback
+     * @param[in] bus             - The D-Bus bus object
+     * @param[in] objPath         - The D-Bus object path
+     * @param[in] versionString   - The version string
+     * @param[in] versionPurpose  - The version purpose
+     * @param[in] extVersion      - The extended version
+     * @param[in] filePath        - The image filesystem path
+     * @param[in] compatibleNames - The device compatibility names
+     * @param[in] callback        - The eraseFunc callback
      */
     Version(sdbusplus::bus::bus& bus, const std::string& objPath,
             const std::string& versionString, VersionPurpose versionPurpose,
             const std::string& extVersion, const std::string& filePath,
-            eraseFunc callback, const std::string& id) :
+            const std::vector<std::string>& compatibleNames, eraseFunc callback,
+            const std::string& id) :
         VersionInherit(bus, (objPath).c_str(), true),
         eraseCallback(callback), id(id), versionStr(versionString)
     {
@@ -88,6 +93,7 @@
         purpose(versionPurpose);
         version(versionString);
         path(filePath);
+        names(compatibleNames);
         // Emit deferred signal.
         emit_object_added();
     }
@@ -101,6 +107,14 @@
                                 std::string key);
 
     /**
+     * @brief Read the manifest file to get the values of the repeated key.
+     *
+     * @return The values of the repeated key.
+     **/
+    static std::vector<std::string>
+        getRepeatedValues(const std::string& manifestFilePath, std::string key);
+
+    /**
      * @brief Calculate the version id from the version string.
      *
      * @details The version id is a unique 8 hexadecimal digit id