config_parser: Add common boolean parser

This more accurately parses systemd values instead of just comparing to
"false" or "true".

Change-Id: I0d70ef418075d555bf6f090fefa0b34332491ed0
Signed-off-by: William A. Kennington III <wak@google.com>
diff --git a/src/config_parser.cpp b/src/config_parser.cpp
index 1d8371d..b684fa2 100644
--- a/src/config_parser.cpp
+++ b/src/config_parser.cpp
@@ -14,6 +14,29 @@
 namespace config
 {
 
+using std::literals::string_view_literals::operator""sv;
+
+bool icaseeq(std::string_view in, std::string_view expected) noexcept
+{
+    return std::equal(in.begin(), in.end(), expected.begin(), expected.end(),
+                      [](auto a, auto b) { return tolower(a) == b; });
+}
+
+std::optional<bool> parseBool(std::string_view in) noexcept
+{
+    if (in == "1"sv || icaseeq(in, "yes"sv) || icaseeq(in, "y"sv) ||
+        icaseeq(in, "true"sv) || icaseeq(in, "t"sv) || icaseeq(in, "on"sv))
+    {
+        return true;
+    }
+    if (in == "0"sv || icaseeq(in, "no"sv) || icaseeq(in, "n"sv) ||
+        icaseeq(in, "false"sv) || icaseeq(in, "f"sv) || icaseeq(in, "off"sv))
+    {
+        return false;
+    }
+    return std::nullopt;
+}
+
 Parser::Parser(const fs::path& filename)
 {
     setFile(filename);
diff --git a/src/config_parser.hpp b/src/config_parser.hpp
index abbf25c..75ef4a7 100644
--- a/src/config_parser.hpp
+++ b/src/config_parser.hpp
@@ -1,6 +1,7 @@
 #pragma once
 
 #include <filesystem>
+#include <optional>
 #include <string>
 #include <string_view>
 #include <unordered_map>
@@ -13,6 +14,11 @@
 namespace config
 {
 
+/** @brief Compare in (case insensitive) vs expected (sensitive) */
+bool icaseeq(std::string_view in, std::string_view expected) noexcept;
+/** @brief Turns a systemd bool string into a c++ bool */
+std::optional<bool> parseBool(std::string_view in) noexcept;
+
 struct string_hash : public std::hash<std::string_view>
 {
     using is_transparent = void;
diff --git a/src/dhcp_configuration.cpp b/src/dhcp_configuration.cpp
index 0605a6a..ec9e6d9 100644
--- a/src/dhcp_configuration.cpp
+++ b/src/dhcp_configuration.cpp
@@ -98,7 +98,14 @@
         log<level::NOTICE>(msg.c_str(), entry("PROP=%s", prop.c_str()));
         return true;
     }
-    return values.back() != "false";
+    auto ret = config::parseBool(values.back());
+    if (!ret.has_value())
+    {
+        auto msg = fmt::format("Failed to parse section DHCP[{}]: `{}`", prop,
+                               values.back());
+        log<level::NOTICE>(msg.c_str(), entry("PROP=%s", prop.c_str()));
+    }
+    return ret.value_or(true);
 }
 } // namespace dhcp
 } // namespace network
diff --git a/src/ethernet_interface.cpp b/src/ethernet_interface.cpp
index 90c987d..06ccc93 100644
--- a/src/ethernet_interface.cpp
+++ b/src/ethernet_interface.cpp
@@ -955,7 +955,15 @@
         log<level::NOTICE>("Unable to get the value for Network[IPv6AcceptRA]");
         return false;
     }
-    return values.back() == "true";
+    auto ret = config::parseBool(values.back());
+    if (!ret.has_value())
+    {
+        auto msg =
+            fmt::format("Failed to parse section Network[IPv6AcceptRA]: `{}`",
+                        values.back());
+        log<level::NOTICE>(msg.c_str());
+    }
+    return ret.value_or(false);
 }
 
 ServerList EthernetInterface::getNTPServersFromConf()
diff --git a/src/util.cpp b/src/util.cpp
index de0bc9d..13be8d0 100644
--- a/src/util.cpp
+++ b/src/util.cpp
@@ -367,8 +367,6 @@
 EthernetInterfaceIntf::DHCPConf getDHCPValue(const std::string& confDir,
                                              const std::string& intf)
 {
-    EthernetInterfaceIntf::DHCPConf dhcp =
-        EthernetInterfaceIntf::DHCPConf::none;
     // Get the interface mode value from systemd conf
     // using namespace std::string_literals;
     fs::path confPath = confDir;
@@ -381,21 +379,25 @@
     if (values.empty())
     {
         log<level::NOTICE>("Unable to get the value for Network[DHCP]");
-        return dhcp;
+        return EthernetInterfaceIntf::DHCPConf::none;
     }
-    if (values.back() == "true")
+    if (config::icaseeq(values.back(), "ipv4"))
     {
-        dhcp = EthernetInterfaceIntf::DHCPConf::both;
+        return EthernetInterfaceIntf::DHCPConf::v4;
     }
-    else if (values.back() == "ipv4")
+    if (config::icaseeq(values.back(), "ipv6"))
     {
-        dhcp = EthernetInterfaceIntf::DHCPConf::v4;
+        return EthernetInterfaceIntf::DHCPConf::v6;
     }
-    else if (values.back() == "ipv6")
+    auto ret = config::parseBool(values.back());
+    if (!ret.has_value())
     {
-        dhcp = EthernetInterfaceIntf::DHCPConf::v6;
+        auto str =
+            fmt::format("Unable to parse Network[DHCP]: `{}`", values.back());
+        log<level::NOTICE>(str.c_str());
     }
-    return dhcp;
+    return ret.value_or(false) ? EthernetInterfaceIntf::DHCPConf::both
+                               : EthernetInterfaceIntf::DHCPConf::none;
 }
 
 namespace mac_address
diff --git a/test/test_config_parser.cpp b/test/test_config_parser.cpp
index e8337af..cd2fa4f 100644
--- a/test/test_config_parser.cpp
+++ b/test/test_config_parser.cpp
@@ -25,6 +25,30 @@
 
 using testing::ElementsAre;
 
+TEST(TestConvert, iCaseEq)
+{
+    EXPECT_TRUE(icaseeq("VaL", "val"));
+    EXPECT_TRUE(icaseeq("[ab1", "[ab1"));
+}
+
+TEST(TestConvert, ParseBool)
+{
+    EXPECT_TRUE(parseBool("tRue").value());
+    EXPECT_FALSE(parseBool("tru").has_value());
+    EXPECT_TRUE(parseBool("t").value());
+    EXPECT_TRUE(parseBool("Yes").value());
+    EXPECT_FALSE(parseBool("ye").has_value());
+    EXPECT_TRUE(parseBool("y").value());
+    EXPECT_TRUE(parseBool("oN").value());
+
+    EXPECT_FALSE(parseBool("fAlse").value());
+    EXPECT_FALSE(parseBool("fal").has_value());
+    EXPECT_FALSE(parseBool("f").value());
+    EXPECT_FALSE(parseBool("No").value());
+    EXPECT_FALSE(parseBool("n").value());
+    EXPECT_FALSE(parseBool("oFf").value());
+}
+
 class TestConfigParser : public stdplus::gtest::TestWithTmp
 {
   public: