Implement the INI config parser

Parse the systemd.network file
to get the configuration parameter.

Change-Id: Ic9c15a46158d2f1c0948e6eca729663e87051491
Signed-off-by: Ratan Gupta <ratagupt@in.ibm.com>
diff --git a/Makefile.am b/Makefile.am
index 02ba4c2..6903a75 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -18,6 +18,7 @@
 		types.hpp \
 		util.hpp \
 		routing_table.hpp \
+		config_parser.hpp \
 		system_configuration.hpp
 
 phosphor_network_manager_SOURCES = \
@@ -30,7 +31,8 @@
 		xyz/openbmc_project/Network/VLAN/Create/server.cpp \
 		xyz/openbmc_project/Network/IP/Create/server.cpp \
 		util.cpp \
-		routing_table.cpp
+		routing_table.cpp \
+		config_parser.cpp
 
 CLEANFILES = \
 		xyz/openbmc_project/Network/VLAN/Create/server.cpp \
diff --git a/config_parser.cpp b/config_parser.cpp
new file mode 100644
index 0000000..51ef55b
--- /dev/null
+++ b/config_parser.cpp
@@ -0,0 +1,165 @@
+#include "config_parser.hpp"
+#include "xyz/openbmc_project/Common/error.hpp"
+#include <phosphor-logging/log.hpp>
+#include <phosphor-logging/elog-errors.hpp>
+
+#include <fstream>
+#include <string>
+#include <algorithm>
+#include <unordered_map>
+#include <regex>
+#include <list>
+
+namespace phosphor
+{
+namespace network
+{
+namespace config
+{
+
+using namespace phosphor::logging;
+using namespace sdbusplus::xyz::openbmc_project::Common::Error;
+
+Parser::Parser(const fs::path& filePath)
+{
+    setFile(filePath);
+}
+
+
+KeyValues Parser::getSection(const std::string& section)
+{
+    auto it = sections.find(section);
+    if (it == sections.end())
+    {
+        log<level::ERR>("ConfigParser: Section not found",
+                        entry("SECTION=%s",section));
+        elog<InternalFailure>();
+    }
+    return it->second;
+}
+
+std::vector<std::string> Parser::getValues(const std::string& section,
+                                           const std::string& key)
+{
+    std::vector<std::string> values;
+    auto keyValues = getSection(section);
+    auto it = keyValues.find(key);
+    if (it == keyValues.end())
+    {
+        log<level::ERR>("ConfigParser: Key not found",
+                        entry("KEY=%s",key));
+        elog<InternalFailure>();
+    }
+    for (; it != keyValues.end() && key == it->first; it++)
+    {
+        values.push_back(it->second);
+    }
+    return values;
+}
+
+
+bool Parser::isValueExist(const std::string& section, const std::string& key,
+                          const std::string& value)
+{
+    try
+    {
+        auto values = getValues(section, key);
+        auto it = std::find(values.begin(), values.end(), value);
+        return it != std::end(values) ? true : false;
+    }
+    catch (InternalFailure& e)
+    {
+        commit<InternalFailure>();
+    }
+    return false;
+}
+
+void Parser::setValue(const std::string& section, const std::string& key,
+                      const std::string& value)
+{
+    KeyValues values;
+    try
+    {
+        values = getSection(section);
+    }
+    catch (InternalFailure& e)
+    {
+        // don't commit the error.
+
+    }
+    values.emplace(key, value);
+    sections.emplace(section, values);
+}
+
+#if 0
+void Parser::print()
+{
+    for (auto section : sections)
+    {
+        std::cout << "[" << section.first << "]\n\n";
+        for (auto keyValue : section.second)
+        {
+            std::cout << keyValue.first << "=" << keyValue.second << "\n";
+        }
+    }
+}
+#endif
+
+void Parser::setFile(const fs::path& filePath)
+{
+    this->filePath = filePath;
+    std::fstream stream;
+    stream.open(filePath.string(), std::fstream::in);
+
+    if (!stream.is_open())
+    {
+        return;
+    }
+    //clear all the section data.
+    sections.clear();
+    parse(stream);
+    stream.close();
+ }
+
+void Parser::parse(std::istream& in)
+{
+    static const std::regex commentRegex
+    {
+        R"x(\s*[;#])x"
+    };
+    static const std::regex sectionRegex
+    {
+        R"x(\s*\[([^\]]+)\])x"
+    };
+    static const std::regex valueRegex
+    {
+        R"x(\s*(\S[^ \t=]*)\s*=\s*(\S+)\s*$)x"
+    };
+    std::string section;
+    std::smatch pieces;
+    for (std::string line; std::getline(in, line);)
+    {
+        if (line.empty() || std::regex_match(line, pieces, commentRegex))
+        {
+            // skip comment lines and blank lines
+        }
+        else if (std::regex_match(line, pieces, sectionRegex))
+        {
+            if (pieces.size() == 2)
+            {
+                section = pieces[1].str();
+            }
+        }
+        else if (std::regex_match(line, pieces, valueRegex))
+        {
+            if (pieces.size() == 3)
+            {
+                setValue(section, pieces[1].str(), pieces[2].str());
+            }
+        }
+    }
+}
+
+}//namespace config
+}//namespace network
+}//namespace phosphor
diff --git a/config_parser.hpp b/config_parser.hpp
new file mode 100644
index 0000000..bf5cb2d
--- /dev/null
+++ b/config_parser.hpp
@@ -0,0 +1,89 @@
+#pragma once
+
+#include <string>
+#include <map>
+#include <unordered_map>
+#include <vector>
+#include <experimental/filesystem>
+
+namespace phosphor
+{
+namespace network
+{
+namespace config
+{
+
+using Section = std::string;
+using KeyValues =  std::multimap<std::string, std::string>;
+namespace fs = std::experimental::filesystem;
+
+class Parser
+{
+    public:
+
+        Parser() = default;
+
+        /** @brief Constructor
+         *  @param[in] fileName - Absolute path of the file which will be parsed.
+         */
+
+        Parser(const fs::path& fileName);
+
+        /** @brief Get the values of the given key and section.
+         *  @param[in] section - section name.
+         *  @param[in] key - key to look for.
+         *  @returns the values associated with the key.
+         */
+
+        std::vector<std::string> getValues(const std::string& section,
+                                           const std::string& key);
+
+        /** @brief Set the value of the given key and section.
+         *  @param[in] section - section name.
+         *  @param[in] key - key name.
+         *  @param[in] value - value.
+         */
+
+        void setValue(const std::string& section, const std::string& key,
+                      const std::string& value);
+
+
+        /** @brief Set the file name and parse it.
+         *  @param[in] fileName - Absolute path of the file.
+         */
+
+        void setFile(const fs::path& fileName);
+
+    private:
+
+        /** @brief Parses the given file and fills the data.
+         *  @param[in] stream - inputstream.
+         */
+
+        void parse(std::istream& stream);
+
+        /** @brief Get all the key values of the given section.
+         *  @param[in] section - section name.
+         *  @returns the map of the key and value.
+         */
+
+        KeyValues getSection(const std::string& section);
+
+        /** @brief checks that whether the value exist in the
+         *         given section.
+         *  @param[in] section - section name.
+         *  @param[in] key - key name.
+         *  @param[in] value - value.
+         *  @returns true if exist otherwise false.
+         */
+
+        bool isValueExist(const std::string& section, const std::string& key,
+                          const std::string& value);
+
+        std::unordered_map<Section, KeyValues> sections;
+        fs::path filePath;
+};
+
+}//namespace config
+}//namespce network
+}//namespace phosphor
diff --git a/test/Makefile.am b/test/Makefile.am
index 8f951bf..bf1a935 100644
--- a/test/Makefile.am
+++ b/test/Makefile.am
@@ -8,7 +8,8 @@
 	test_util.cpp \
 	mock_syscall.cpp \
 	test_network_manager.cpp \
-	test_ethernet_interface.cpp
+	test_ethernet_interface.cpp \
+	test_config_parser.cpp
 
 test_CPPFLAGS = -Igtest $(GTEST_CPPFLAGS) $(AM_CPPFLAGS)
 
@@ -32,5 +33,6 @@
 			$(top_builddir)/routing_table.cpp \
 			$(top_builddir)/util.cpp \
 			$(top_builddir)/system_configuration.cpp \
+			$(top_builddir)/config_parser.cpp \
 			$(top_builddir)/xyz/openbmc_project/Network/VLAN/Create/phosphor_network_manager-server.o \
 			$(top_builddir)/xyz/openbmc_project/Network/IP/Create/phosphor_network_manager-server.o
diff --git a/test/test_config_parser.cpp b/test/test_config_parser.cpp
new file mode 100644
index 0000000..a07390b
--- /dev/null
+++ b/test/test_config_parser.cpp
@@ -0,0 +1,98 @@
+#include <gtest/gtest.h>
+
+#include "config_parser.hpp"
+
+#include "xyz/openbmc_project/Common/error.hpp"
+#include <phosphor-logging/elog-errors.hpp>
+
+#include "config.h"
+#include <exception>
+#include <stdexcept>
+#include <fstream>
+
+namespace phosphor
+{
+namespace network
+{
+
+class TestConfigParser : public testing::Test
+{
+    public:
+        config::Parser parser;
+        TestConfigParser()
+        {
+            remove("/tmp/eth0.network");
+            std::ofstream filestream("/tmp/eth0.network");
+
+            filestream << "[Match]\nName=eth0\n" <<
+                          "[Network]\nDHCP=true\n[DHCP]\nClientIdentifier= mac\n";
+            filestream.close();
+            parser.setFile("/tmp/eth0.network");
+        }
+
+        bool isValueFound(const std::vector<std::string>& values,
+                          const std::string& expectedValue)
+        {
+            for (const auto& value : values)
+            {
+                if (expectedValue == value)
+                {
+                    return  true;
+                }
+            }
+            return false;
+        }
+};
+
+TEST_F(TestConfigParser, ReadConfigDataFromFile)
+{
+    auto values = parser.getValues("Network", "DHCP");
+    std::string expectedValue = "true";
+    bool found = isValueFound(values, expectedValue);
+    EXPECT_EQ(found, true);
+
+    values = parser.getValues("DHCP", "ClientIdentifier");
+    expectedValue = "mac";
+    found = isValueFound(values, expectedValue);
+    EXPECT_EQ(found, true);
+
+    values = parser.getValues("Match", "Name");
+    expectedValue = "eth0";
+    found = isValueFound(values, expectedValue);
+    EXPECT_EQ(found, true);
+}
+
+TEST_F(TestConfigParser, SectionNotExist)
+{
+    using namespace sdbusplus::xyz::openbmc_project::Common::Error;
+    bool caughtException = false;
+    try
+    {
+        parser.getValues("abc", "ipaddress");
+    }
+    catch (const std::exception& e)
+    {
+        caughtException = true;
+    }
+    EXPECT_EQ(true, caughtException);
+}
+
+TEST_F(TestConfigParser, KeyNotFound)
+{
+    using namespace sdbusplus::xyz::openbmc_project::Common::Error;
+    bool caughtException = false;
+    try
+    {
+        parser.getValues("Network", "abc");
+    }
+    catch (const std::exception& e)
+    {
+        caughtException = true;
+    }
+    EXPECT_EQ(true, caughtException);
+    remove("/tmp/eth0.network");
+}
+
+}//namespace network
+}//namespace phosphor
+