BMC Minimum ship Level

This code compares the BMC_MSL defined at compile time,
It will parse the new fw (explicit at MANIFEST file) and use regex to
evaluate it  and compare BMC_MSL against version from MANIFEST.
If newer or equal  it will apply, otherwise it will fail,
preventing activation  operation.

Tested:
 regex-bmc-msl="([a-z]+[0-9]{2})+([0-9]+).([0-9]+).([0-9]+)"
 fw-package="version=fw1010.00-28.4-0-ge611abca21"
 bmc-msl="fw1010.00-27"  proceeds with activation...
 bmc-msl="fw1010.00-29"  returns:

Jul 15 20:35:45 tacoma1z-w81 phosphor-image-updater[766]:
BMC Minimum Ship Level NOT met

Jul 15 20:35:45 tacoma1z-w81 phosphor-image-updater[766]:
A system component has a software version that is incompatible as
determined by the implementation and needs to be updated....

Signed-off-by: Miguel Gomez <mgomez@mx1.ibm.com>
Change-Id: I0ab0eba7c7c89f38ca698aa3e369aa50797edb07
diff --git a/activation.cpp b/activation.cpp
index 6b9dfb8..79b5b10 100644
--- a/activation.cpp
+++ b/activation.cpp
@@ -2,6 +2,7 @@
 
 #include "images.hpp"
 #include "item_updater.hpp"
+#include "msl_verify.hpp"
 #include "serialize.hpp"
 
 #include <phosphor-logging/elog-errors.hpp>
@@ -9,6 +10,7 @@
 #include <phosphor-logging/log.hpp>
 #include <sdbusplus/exception.hpp>
 #include <xyz/openbmc_project/Common/error.hpp>
+#include <xyz/openbmc_project/Software/Version/error.hpp>
 
 #ifdef WANT_SIGNATURE_VERIFY
 #include "image_verify.hpp"
@@ -78,7 +80,6 @@
 
 auto Activation::activation(Activations value) -> Activations
 {
-
     if ((value != softwareServer::Activation::Activations::Active) &&
         (value != softwareServer::Activation::Activations::Activating))
     {
@@ -111,6 +112,24 @@
         }
 #endif
 
+        auto versionStr = parent.versions.find(versionId)->second->version();
+
+        if (!minimum_ship_level::verify(versionStr))
+        {
+            using namespace phosphor::logging;
+            using IncompatibleErr = sdbusplus::xyz::openbmc_project::Software::
+                Version::Error::Incompatible;
+            using Incompatible =
+                xyz::openbmc_project::Software::Version::Incompatible;
+
+            report<IncompatibleErr>(
+                prev_entry<Incompatible::MIN_VERSION>(),
+                prev_entry<Incompatible::ACTUAL_VERSION>(),
+                prev_entry<Incompatible::VERSION_PURPOSE>());
+            return softwareServer::Activation::activation(
+                softwareServer::Activation::Activations::Failed);
+        }
+
 #ifdef WANT_SIGNATURE_VERIFY
         fs::path uploadDir(IMG_UPLOAD_DIR);
         if (!verifySignature(uploadDir / versionId, SIGNED_IMAGE_CONF_PATH))
diff --git a/meson.build b/meson.build
index 1923fb8..c74a927 100644
--- a/meson.build
+++ b/meson.build
@@ -72,6 +72,9 @@
 conf.set_quoted('SIGNED_IMAGE_CONF_PATH', get_option('signed-image-conf-path'))
 conf.set_quoted('SYNC_LIST_DIR_PATH', get_option('sync-list-dir-path'))
 conf.set_quoted('SYNC_LIST_FILE_NAME', get_option('sync-list-file-name'))
+conf.set_quoted('BMC_MSL', get_option('bmc-msl'))
+conf.set_quoted('REGEX_BMC_MSL', get_option('regex-bmc-msl'))
+
 
 configure_file(output: 'config.h', configuration: conf)
 
@@ -108,7 +111,8 @@
     'item_updater_main.cpp',
     'serialize.cpp',
     'version.cpp',
-    'utils.cpp'
+    'utils.cpp',
+    'msl_verify.cpp'
 )
 
 if get_option('bmc-layout').contains('static')
diff --git a/meson_options.txt b/meson_options.txt
index c0def0c..0043434 100644
--- a/meson_options.txt
+++ b/meson_options.txt
@@ -89,3 +89,15 @@
     value: 'synclist',
     description: 'The name of the sync list file.',
 )
+
+option(
+    'bmc-msl', type: 'string',
+    value: '',
+    description: 'The BMC minimum ship level.',
+)
+
+option(
+    'regex-bmc-msl', type: 'string',
+    value: '',
+    description: 'The Regular expression to parse the MSL.',
+)
diff --git a/msl_verify.cpp b/msl_verify.cpp
new file mode 100644
index 0000000..3cd2278
--- /dev/null
+++ b/msl_verify.cpp
@@ -0,0 +1,96 @@
+#include "config.h"

+

+#include "msl_verify.hpp"

+

+#include "version.hpp"

+

+#include <phosphor-logging/log.hpp>

+

+#include <regex>

+

+using namespace phosphor::logging;

+

+int minimum_ship_level::compare(const Version& versionToCompare,

+                                const Version& mslVersion)

+{

+    if (versionToCompare.major > mslVersion.major)

+        return (1);

+    if (versionToCompare.major < mslVersion.major)

+        return (-1);

+

+    if (versionToCompare.minor > mslVersion.minor)

+        return (1);

+    if (versionToCompare.minor < mslVersion.minor)

+        return (-1);

+

+    if (versionToCompare.rev > mslVersion.rev)

+        return (1);

+    if (versionToCompare.rev < mslVersion.rev)

+        return (-1);

+

+    // Both string are equal and there is no need to make an upgrade return 0.

+    return 0;

+}

+

+// parse Function copy  inpVersion onto outVersion in Version format

+// {major,minor,rev}.

+void minimum_ship_level::parse(const std::string& inpVersion,

+                               Version& outVersion)

+{

+    std::smatch match;

+    outVersion = {0, 0, 0};

+

+    std::regex rx{REGEX_BMC_MSL, std::regex::extended};

+

+    if (!std::regex_search(inpVersion, match, rx))

+    {

+        log<level::ERR>("Unable to parse BMC version",

+                        entry("VERSION=%s", inpVersion.c_str()));

+        return;

+    }

+

+    outVersion.major = std::stoi(match[2]);

+    outVersion.minor = std::stoi(match[3]);

+    outVersion.rev = std::stoi(match[4]);

+}

+

+bool minimum_ship_level::verify(const std::string& versionManifest)

+{

+

+    //  If there is no msl or mslRegex return upgrade is needed.

+    std::string msl{BMC_MSL};

+    std::string mslRegex{REGEX_BMC_MSL};

+    if (msl.empty() || mslRegex.empty())

+    {

+        return true;

+    }

+

+    // Define mslVersion variable and populate in Version format

+    // {major,minor,rev} using parse function.

+

+    Version mslVersion = {0, 0, 0};

+    parse(msl, mslVersion);

+

+    // Define actualVersion variable and populate in Version format

+    // {major,minor,rev} using parse function.

+    std::string tmpStr{};

+

+    tmpStr = versionManifest;

+    Version actualVersion = {0, 0, 0};

+    parse(versionManifest, actualVersion);

+

+    // Compare actualVersion vs MSL.

+    auto rc = compare(actualVersion, mslVersion);

+    if (rc < 0)

+    {

+        log<level::ERR>(

+            "BMC Minimum Ship Level NOT met",

+            entry("MIN_VERSION=%s", msl.c_str()),

+            entry("ACTUAL_VERSION=%s", tmpStr.c_str()),

+            entry("VERSION_PURPOSE=%s",

+                  "xyz.openbmc_project.Software.Version.VersionPurpose.BMC"));

+        return false;

+    }

+

+    return true;

+}

diff --git a/msl_verify.hpp b/msl_verify.hpp
new file mode 100644
index 0000000..9fd6695
--- /dev/null
+++ b/msl_verify.hpp
@@ -0,0 +1,40 @@
+#pragma once

+

+#include <string>

+

+namespace minimum_ship_level

+{

+

+/** @brief Version components */

+struct Version

+{

+    uint8_t major;

+    uint8_t minor;

+    uint8_t rev;

+};

+

+/** @brief Verify if the current BMC version meets the min ship level

+ *  @return true if the verification succeeded, false otherwise

+ */

+bool verify(const std::string& versionStr);

+

+/** @brief Parse the version components into a struct

+ *  @details User passes a version string in regex format (REGEX_BMC_MSL)

+ *  at compilation time, this value is break down by parse function to allocate

+ *  a struct so it can be compared position by position against the (BMC_MSL)

+ *  also defined at compile time.

+ * @param[in]  versionStr - The version string to be parsed

+ * @param[out] version    - The version struct to be populated

+ */

+void parse(const std::string& versionStr, Version& version);

+

+/** @brief Compare the versions provided

+ *  @param[in] a - The first version to compare

+ *  @param[in] b - The second version to compare

+ *  @return 1 if a > b

+ *          0 if a = b

+ *         -1 if a < b

+ */

+int compare(const Version& a, const Version& b);

+

+} // namespace minimum_ship_level