types: Add constexpr in6_addr parser

Change-Id: Icd9c42e22e0eb3dcde86b7ba65208975813e7643
Signed-off-by: William A. Kennington III <wak@google.com>
diff --git a/src/types.hpp b/src/types.hpp
index 1a87faa..70d9e0a 100644
--- a/src/types.hpp
+++ b/src/types.hpp
@@ -309,6 +309,66 @@
     }
 };
 
+template <>
+struct ToAddr<in6_addr>
+{
+    constexpr in6_addr operator()(std::string_view str) const
+    {
+        constexpr DecodeInt<uint16_t, 16> di;
+        in6_addr ret = {};
+        size_t i = 0;
+        while (i < 8)
+        {
+            auto loc = str.find(':');
+            if (i == 6 && loc == str.npos)
+            {
+                ret.s6_addr32[3] = ToAddr<in_addr>{}(str).s_addr;
+                return ret;
+            }
+            if (loc != 0 && !str.empty())
+            {
+                ret.s6_addr16[i++] = hton(di(str.substr(0, loc)));
+            }
+            if (i < 8 && str.size() > loc + 1 && str[loc + 1] == ':')
+            {
+                str.remove_prefix(loc + 2);
+                break;
+            }
+            else if (str.empty())
+            {
+                throw std::invalid_argument("IPv6 Data");
+            }
+            str.remove_prefix(loc == str.npos ? str.size() : loc + 1);
+        }
+        if (str.starts_with(':'))
+        {
+            throw std::invalid_argument("Extra separator");
+        }
+        size_t j = 7;
+        if (!str.empty() && i < 6 && str.find('.') != str.npos)
+        {
+            auto loc = str.rfind(':');
+            ret.s6_addr32[3] =
+                ToAddr<in_addr>{}(str.substr(loc == str.npos ? 0 : loc + 1))
+                    .s_addr;
+            str.remove_suffix(loc == str.npos ? str.size() : str.size() - loc);
+            j -= 2;
+        }
+        while (!str.empty() && j > i)
+        {
+            auto loc = str.rfind(':');
+            ret.s6_addr16[j--] =
+                hton(di(str.substr(loc == str.npos ? 0 : loc + 1)));
+            str.remove_suffix(loc == str.npos ? str.size() : str.size() - loc);
+        }
+        if (!str.empty())
+        {
+            throw std::invalid_argument("Too much data");
+        }
+        return ret;
+    }
+};
+
 namespace detail
 {
 
diff --git a/test/test_types.cpp b/test/test_types.cpp
index 09a9666..b72ecfa 100644
--- a/test/test_types.cpp
+++ b/test/test_types.cpp
@@ -136,6 +136,51 @@
     EXPECT_EQ((in_addr{htonl(0xc0a80101)}), ToAddr<in_addr>{}("192.168.001.1"));
 }
 
+TEST(ToAddr, In6Addr)
+{
+    constexpr ToAddr<in6_addr> ta;
+    EXPECT_THROW(ta(""), std::invalid_argument);
+    EXPECT_THROW(ta("0"), std::invalid_argument);
+    EXPECT_THROW(ta("0:0"), std::invalid_argument);
+    EXPECT_THROW(ta("0::0:"), std::invalid_argument);
+    EXPECT_THROW(ta("0:::"), std::invalid_argument);
+    EXPECT_THROW(ta(":::0"), std::invalid_argument);
+    EXPECT_THROW(ta("0:::0"), std::invalid_argument);
+    EXPECT_THROW(ta("0::0::0"), std::invalid_argument);
+    EXPECT_THROW(ta("1::0.0.0."), std::invalid_argument);
+    EXPECT_THROW(ta("1::.0.0.0"), std::invalid_argument);
+    EXPECT_THROW(ta("x::0"), std::invalid_argument);
+    EXPECT_THROW(ta("g::0"), std::invalid_argument);
+    EXPECT_THROW(ta("0:1:2:3:4::5:6:7"), std::invalid_argument);
+    EXPECT_THROW(ta("::0:1:2:3:4:5:6:7"), std::invalid_argument);
+    EXPECT_THROW(ta("0:1:2:3:4:5:6:7::"), std::invalid_argument);
+    EXPECT_THROW(ta("0:1:2:3:4:5:6:7:8"), std::invalid_argument);
+    EXPECT_THROW(ta("0:1:2:3:4:5:6:0.0.0.0"), std::invalid_argument);
+    EXPECT_THROW(ta("0:1:2:3:4:5::0.0.0.0"), std::invalid_argument);
+    EXPECT_THROW(ta("ffff0::0"), std::overflow_error);
+
+    EXPECT_EQ((in6_addr{}), ta("::"));
+    EXPECT_EQ((in6_addr{}), ta("0:0:0:0:0:0:0:0"));
+    EXPECT_EQ((in6_addr{0, 0xff}), ta("ff::"));
+    EXPECT_EQ((in6_addr{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0xff}),
+              ta("::ff"));
+    EXPECT_EQ((in6_addr{0, 0, 0, 0, 0, 0xff, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0xff}),
+              ta("0:0:ff::ff"));
+    EXPECT_EQ((in6_addr{0, 1, 0, 2, 0, 3, 0, 4, 0, 0, 0, 6, 0, 7, 0, 8}),
+              ta("1:2:3:4::6:7:8"));
+    EXPECT_EQ((in6_addr{0, 1, 0, 2, 0, 3, 0, 4, 0, 5, 0, 6, 0, 7, 0, 0}),
+              ta("1:2:3:4:5:6:7::"));
+    EXPECT_EQ((in6_addr{0, 0, 0, 2, 0, 3, 0, 4, 0, 5, 0, 6, 0, 7, 0, 8}),
+              ta("::2:3:4:5:6:7:8"));
+    EXPECT_EQ(
+        (in6_addr{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0xff, 0xff, 192, 168, 0, 1}),
+        ta("::ffff:192.168.0.1"));
+    EXPECT_EQ((in6_addr{0, 0xff, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 255, 168, 0, 1}),
+              ta("ff::255.168.0.1"));
+    EXPECT_EQ((in6_addr{0, 0, 0, 1, 0, 2, 0, 3, 0, 4, 0, 5, 255, 168, 0, 1}),
+              ta("0:1:2:3:4:5:255.168.0.1"));
+}
+
 namespace detail
 {