apphandler: use regex to parse firmware version

1. Use regular expression to parse firmware major & minor version.
2. Add meson option to modify regex string by platform layer.

Tested result:

- parsing result of version 1: v0.6-19-gf363f61-dirty
rev.major = 0
rev.minor = 6

- parsing result of version 2: v1.99.10-113-g65edf7d-r3-0-g9e4f715
rev.major = 1
rev.minor = 99

- parsing result of version 3: 2.14.0-dev
rev.major = 2
rev.minor = 14

Change-Id: I1a5ebd592e3078d38182926a6a9ee98a129c3ee4
Signed-off-by: Potin Lai <potin.lai@quantatw.com>
diff --git a/apphandler.cpp b/apphandler.cpp
index fe74bb0..8ad2879 100644
--- a/apphandler.cpp
+++ b/apphandler.cpp
@@ -1,3 +1,5 @@
+#include "config.h"
+
 #include <arpa/inet.h>
 #include <fcntl.h>
 #include <limits.h>
@@ -29,6 +31,7 @@
 #include <nlohmann/json.hpp>
 #include <phosphor-logging/elog-errors.hpp>
 #include <phosphor-logging/log.hpp>
+#include <regex>
 #include <sdbusplus/message/types.hpp>
 #include <string>
 #include <string_view>
@@ -468,129 +471,51 @@
     uint16_t d[2];
 } Revision;
 
-/* Currently supports the vx.x-x-[-x] and v1.x.x-x-[-x] format. It will     */
-/* return -1 if not in those formats, this routine knows how to parse       */
-/* version = v0.6-19-gf363f61-dirty                                         */
-/*            ^ ^ ^^          ^                                             */
-/*            | |  |----------|-- additional details                        */
-/*            | |---------------- Minor                                     */
-/*            |------------------ Major                                     */
-/* and version = v1.99.10-113-g65edf7d-r3-0-g9e4f715                        */
-/*                ^ ^  ^^ ^                                                 */
-/*                | |  |--|---------- additional details                    */
-/*                | |---------------- Minor                                 */
-/*                |------------------ Major                                 */
-/* Additional details : If the option group exists it will force Auxiliary  */
-/* Firmware Revision Information 4th byte to 1 indicating the build was     */
-/* derived with additional edits                                            */
-int convertVersion(std::string_view s, Revision& rev)
+/* Use regular expression searching matched pattern X.Y, and convert it to  */
+/* Major (X) and Minor (Y) version.                                         */
+/* Example:                                                                 */
+/* version = 2.14.0-dev                                                     */
+/*           ^ ^                                                            */
+/*           | |---------------- Minor                                      */
+/*           |------------------ Major                                      */
+/*                                                                          */
+int convertVersion(std::string s, Revision& rev)
 {
-    std::string_view token;
-    uint16_t commits;
+    std::regex fw_regex(FW_VER_REGEX);
+    std::smatch m;
+    Revision r = {0};
+    size_t val;
 
-    auto location = s.find_first_of('v');
-    if (location != std::string::npos)
+    while (std::regex_search(s, m, fw_regex))
     {
-        s = s.substr(location + 1);
+        // convert major
+        {
+            std::string_view str = m[1].str();
+            auto [ptr, ec]{std::from_chars(str.begin(), str.end(), val)};
+            if (ec != std::errc() || ptr != str.begin() + str.size())
+            { // failed to convert major string
+                continue;
+            }
+            r.major = val & 0x7F;
+        }
+
+        // convert minor
+        {
+            std::string_view str = m[2].str();
+            auto [ptr, ec]{std::from_chars(str.begin(), str.end(), val)};
+            if (ec != std::errc() || ptr != str.begin() + str.size())
+            { // failed to convert minor string
+                continue;
+            }
+            r.minor = val & 0xFF;
+        }
+
+        // all matched
+        rev = r;
+        return 0;
     }
 
-    if (!s.empty())
-    {
-        location = s.find_first_of(".");
-        if (location != std::string::npos)
-        {
-            std::string_view majorView = s.substr(0, location);
-            auto [ptr, ec]{
-                std::from_chars(majorView.begin(), majorView.end(), rev.major)};
-            if (ec != std::errc())
-            {
-                throw std::runtime_error(
-                    "failed to convert major string to uint8_t: " +
-                    std::make_error_code(ec).message());
-            }
-            if (ptr != majorView.begin() + majorView.size())
-            {
-                throw std::runtime_error(
-                    "converted invalid characters in major string");
-            }
-            token = s.substr(location + 1);
-        }
-
-        if (!token.empty())
-        {
-            location = token.find_first_of(".-");
-            if (location != std::string::npos)
-            {
-                std::string_view minorView = token.substr(0, location);
-                auto [ptr, ec]{std::from_chars(minorView.begin(),
-                                               minorView.end(), rev.minor)};
-                if (ec != std::errc())
-                {
-                    throw std::runtime_error(
-                        "failed to convert minor string to uint8_t: " +
-                        std::make_error_code(ec).message());
-                }
-                if (ptr != minorView.begin() + minorView.size())
-                {
-                    throw std::runtime_error(
-                        "converted invalid characters in minor string");
-                }
-                token = token.substr(location + 1);
-            }
-        }
-
-        // Capture the number of commits on top of the minor tag.
-        // I'm using BE format like the ipmi spec asked for
-        location = token.find_first_of(".-");
-        if (!token.empty())
-        {
-            std::string_view commitView = token.substr(0, location);
-            auto [ptr, ec]{std::from_chars(commitView.begin(), commitView.end(),
-                                           commits, 16)};
-            if (ec != std::errc())
-            {
-                throw std::runtime_error(
-                    "failed to convert commit string to uint16_t: " +
-                    std::make_error_code(ec).message());
-            }
-            if (ptr != commitView.begin() + commitView.size())
-            {
-                throw std::runtime_error(
-                    "converted invalid characters in commit string");
-            }
-            rev.d[0] = (commits >> 8) | (commits << 8);
-
-            // commit number we skip
-            location = token.find_first_of(".-");
-            if (location != std::string::npos)
-            {
-                token = token.substr(location + 1);
-            }
-        }
-        else
-        {
-            rev.d[0] = 0;
-        }
-
-        if (location != std::string::npos)
-        {
-            token = token.substr(location + 1);
-        }
-
-        // Any value of the optional parameter forces it to 1
-        location = token.find_first_of(".-");
-        if (location != std::string::npos)
-        {
-            token = token.substr(location + 1);
-        }
-        commits = (!token.empty()) ? 1 : 0;
-
-        // We do this operation to get this displayed in least significant bytes
-        // of ipmitool device id command.
-        rev.d[1] = (commits >> 8) | (commits << 8);
-    }
-
-    return 0;
+    return -1;
 }
 
 /* @brief: Implement the Get Device ID IPMI command per the IPMI spec
diff --git a/meson.build b/meson.build
index 4889b97..55d25b0 100644
--- a/meson.build
+++ b/meson.build
@@ -32,6 +32,7 @@
 conf_data.set_quoted('HOST_NAME', get_option('host-name'))
 conf_data.set_quoted('POWER_READING_SENSOR', get_option('power-reading-sensor'))
 conf_data.set_quoted('HOST_IPMI_LIB_PATH', get_option('host-ipmi-lib-path'))
+conf_data.set_quoted('FW_VER_REGEX', get_option('fw-ver-regex'))
 
 conf_h = configure_file(
   output: 'config.h',
diff --git a/meson_options.txt b/meson_options.txt
index 24ba60b..cbfee77 100644
--- a/meson_options.txt
+++ b/meson_options.txt
@@ -67,3 +67,4 @@
 
 # Software Version
 option('get-dbus-active-software', type: 'feature', description: 'Use the  getActiveSoftwareVersionInfo for the BMC version and dev_id.json as backup')
+option('fw-ver-regex', type : 'string', value : '(\\\\d+)\\\\.(\\\\d+)', description : 'Regular expressions for parsing firmware revision')
\ No newline at end of file