rsyslog-config: Support IPv6 address

The IPv6 address requires `[]` in the config file. Add support for this
format so that user could set IPv6 address on DBus, and this service
will add `[]` in the config file to make it work correctly.

Split the logic in restore() into a separate function parseConfig() so
that it is easier to test.

Tested: Verify both IPv4 and IPv6 address could be set to rsyslog-config
        and verify it works correctly.
        Added several IPv6 related test cases.

Signed-off-by: Lei YU <yulei.sh@bytedance.com>
Change-Id: I2135e3b0e916947449ab5d0cfa9669a98349226e
diff --git a/phosphor-rsyslog-config/server-conf.cpp b/phosphor-rsyslog-config/server-conf.cpp
index 61bfaf0..9ed18e8 100644
--- a/phosphor-rsyslog-config/server-conf.cpp
+++ b/phosphor-rsyslog-config/server-conf.cpp
@@ -13,6 +13,7 @@
 #include <arpa/inet.h>
 #include <netdb.h>
 
+#include <optional>
 #include <string>
 
 namespace phosphor
@@ -24,6 +25,82 @@
 using namespace phosphor::logging;
 using namespace sdbusplus::xyz::openbmc_project::Common::Error;
 
+namespace internal
+{
+
+bool isIPv6Address(const std::string& addr)
+{
+    struct in6_addr result;
+    return inet_pton(AF_INET6, addr.c_str(), &result) == 1;
+}
+
+std::optional<std::pair<std::string, uint32_t>> parseConfig(std::istream& ss)
+{
+    std::string line;
+    std::getline(ss, line);
+
+    //"*.* @@<address>:<port>" or
+    //"*.* @@[<ipv6-address>:<port>"
+    constexpr auto start = 6; // Skip "*.* @@"
+    std::string serverAddress;
+    std::string serverPort;
+
+    // Ignore if line is commented
+    if (!line.empty() && '#' != line.at(0))
+    {
+        // Check if there is "[]", and make IPv6 address from it
+        auto posColonLeft = line.find('[');
+        auto posColonRight = line.find(']');
+        if (posColonLeft != std::string::npos ||
+            posColonRight != std::string::npos)
+        {
+            // It contains [ or ], so it should be an IPv6 address
+            if (posColonLeft == std::string::npos ||
+                posColonRight == std::string::npos)
+            {
+                // There either '[' or ']', invalid config
+                return {};
+            }
+            if (line.size() < posColonRight + 2 ||
+                line.at(posColonRight + 1) != ':')
+            {
+                // There is no ':', or no more content after ':', invalid config
+                return {};
+            }
+            serverAddress =
+                line.substr(posColonLeft + 1, posColonRight - posColonLeft - 1);
+            serverPort = line.substr(posColonRight + 2);
+        }
+        else
+        {
+            auto pos = line.find(':');
+            if (pos == std::string::npos)
+            {
+                // There is no ':', invalid config
+                return {};
+            }
+            serverAddress = line.substr(start, pos - start);
+            serverPort = line.substr(pos + 1);
+        }
+    }
+    if (serverAddress.empty() || serverPort.empty())
+    {
+        return {};
+    }
+    try
+    {
+        uint32_t port = std::stoul(serverPort);
+        return std::make_pair(std::move(serverAddress), port);
+    }
+    catch (const std::exception& ex)
+    {
+        log<level::ERR>("Invalid config", entry("ERR=%s", ex.what()));
+        return {};
+    }
+}
+
+} // namespace internal
+
 std::string Server::address(std::string value)
 {
     using Argument = xyz::openbmc_project::Common::InvalidArgument;
@@ -99,7 +176,14 @@
     if (serverPort && !serverAddress.empty())
     {
         // write '*.* @@<remote-host>:<port>'
-        stream << "*.* @@" << serverAddress << ":" << serverPort;
+        if (internal::isIPv6Address(serverAddress))
+        {
+            stream << "*.* @@[" << serverAddress << "]:" << serverPort;
+        }
+        else
+        {
+            stream << "*.* @@" << serverAddress << ":" << serverPort;
+        }
     }
     else // this is a disable request
     {
@@ -133,23 +217,12 @@
 void Server::restore(const char* filePath)
 {
     std::fstream stream(filePath, std::fstream::in);
-    std::string line;
 
-    std::getline(stream, line);
-
-    // Ignore if line is commented
-    if ('#' != line.at(0))
+    auto ret = internal::parseConfig(stream);
+    if (ret)
     {
-        auto pos = line.find(':');
-        if (pos != std::string::npos)
-        {
-            //"*.* @@<address>:<port>"
-            constexpr auto start = 6; // Skip "*.* @@"
-            auto serverAddress = line.substr(start, pos - start);
-            auto serverPort = line.substr(pos + 1);
-            NetworkClient::address(std::move(serverAddress));
-            NetworkClient::port(std::stoul(serverPort));
-        }
+        NetworkClient::address(ret->first);
+        NetworkClient::port(ret->second);
     }
 }