net/addr/ip: Add IPv6 FromStr conversion

Change-Id: I453817ebe132313e992f47234ede04f9101673d4
Signed-off-by: William A. Kennington III <wak@google.com>
diff --git a/include/stdplus/net/addr/ip.hpp b/include/stdplus/net/addr/ip.hpp
index 59a8f2f..b312536 100644
--- a/include/stdplus/net/addr/ip.hpp
+++ b/include/stdplus/net/addr/ip.hpp
@@ -120,6 +120,67 @@
     }
 };
 
+template <>
+struct FromStr<In6Addr>
+{
+    template <typename CharT>
+    constexpr In6Addr operator()(std::basic_string_view<CharT> sv) const
+    {
+        constexpr StrToInt<16, uint16_t> sti;
+        constexpr FromStr<In4Addr> fsip4;
+        in6_addr ret = {};
+        size_t i = 0;
+        while (i < 8)
+        {
+            auto loc = sv.find(':');
+            if (i == 6 && loc == sv.npos)
+            {
+                ret.s6_addr32[3] = fsip4(sv).s4_addr32;
+                return ret;
+            }
+            if (loc != 0 && !sv.empty())
+            {
+                ret.s6_addr16[i++] = hton(sti(sv.substr(0, loc)));
+            }
+            if (i < 8 && sv.size() > loc + 1 && sv[loc + 1] == ':')
+            {
+                sv.remove_prefix(loc + 2);
+                break;
+            }
+            else if (sv.empty())
+            {
+                throw std::invalid_argument("IPv6 Data");
+            }
+            sv.remove_prefix(loc == sv.npos ? sv.size() : loc + 1);
+        }
+        if (sv.starts_with(':'))
+        {
+            throw std::invalid_argument("Extra separator");
+        }
+        size_t j = 7;
+        if (!sv.empty() && i < 6 && sv.find('.') != sv.npos)
+        {
+            auto loc = sv.rfind(':');
+            ret.s6_addr32[3] =
+                fsip4(sv.substr(loc == sv.npos ? 0 : loc + 1)).s4_addr32;
+            sv.remove_suffix(loc == sv.npos ? sv.size() : sv.size() - loc);
+            j -= 2;
+        }
+        while (!sv.empty() && j > i)
+        {
+            auto loc = sv.rfind(':');
+            ret.s6_addr16[j--] =
+                hton(sti(sv.substr(loc == sv.npos ? 0 : loc + 1)));
+            sv.remove_suffix(loc == sv.npos ? sv.size() : sv.size() - loc);
+        }
+        if (!sv.empty())
+        {
+            throw std::invalid_argument("Too much data");
+        }
+        return ret;
+    }
+};
+
 namespace detail
 {
 using InAnyAddrV = std::variant<In4Addr, In6Addr>;
diff --git a/src/net/addr/ip.cpp b/src/net/addr/ip.cpp
index fca752a..16e328f 100644
--- a/src/net/addr/ip.cpp
+++ b/src/net/addr/ip.cpp
@@ -4,4 +4,5 @@
 {
 template In4Addr FromStr<In4Addr>::operator()(std::string_view) const;
 template char* ToStr<In4Addr>::operator()(char*, In4Addr) const noexcept;
+template In6Addr FromStr<In6Addr>::operator()(std::string_view) const;
 } // namespace stdplus
diff --git a/test/net/addr/ip.cpp b/test/net/addr/ip.cpp
index 0a2bc30..3e02404 100644
--- a/test/net/addr/ip.cpp
+++ b/test/net/addr/ip.cpp
@@ -3,8 +3,12 @@
 #include <stdplus/net/addr/ip.hpp>
 #include <stdplus/numeric/endian.hpp>
 
+#include <string_view>
+
 #include <gtest/gtest.h>
 
+using std::literals::string_view_literals::operator""sv;
+
 namespace stdplus
 {
 
@@ -50,6 +54,54 @@
     std::hash<In6Addr>{}(In6Addr{});
 }
 
+TEST(FromStr, In6Addr)
+{
+    constexpr FromStr<In6Addr> fs;
+    EXPECT_THROW(fs(""sv), std::invalid_argument);
+    EXPECT_THROW(fs("0"sv), std::invalid_argument);
+    EXPECT_THROW(fs("0:0"sv), std::invalid_argument);
+    EXPECT_THROW(fs("0::0:"sv), std::invalid_argument);
+    EXPECT_THROW(fs("0:::"sv), std::invalid_argument);
+    EXPECT_THROW(fs(":::0"sv), std::invalid_argument);
+    EXPECT_THROW(fs("0:::0"sv), std::invalid_argument);
+    EXPECT_THROW(fs("0::0::0"sv), std::invalid_argument);
+    EXPECT_THROW(fs("1::0.0.0."sv), std::invalid_argument);
+    EXPECT_THROW(fs("1::.0.0.0"sv), std::invalid_argument);
+    EXPECT_THROW(fs("x::0"sv), std::invalid_argument);
+    EXPECT_THROW(fs("g::0"sv), std::invalid_argument);
+    EXPECT_THROW(fs("0:1:2:3:4::5:6:7"sv), std::invalid_argument);
+    EXPECT_THROW(fs("::0:1:2:3:4:5:6:7"sv), std::invalid_argument);
+    EXPECT_THROW(fs("0:1:2:3:4:5:6:7::"sv), std::invalid_argument);
+    EXPECT_THROW(fs("0:1:2:3:4:5:6:7:8"sv), std::invalid_argument);
+    EXPECT_THROW(fs("0:1:2:3:4:5:6:0.0.0.0"sv), std::invalid_argument);
+    EXPECT_THROW(fs("0:1:2:3:4:5::0.0.0.0"sv), std::invalid_argument);
+    EXPECT_THROW(fs("ffff0::0"sv), std::overflow_error);
+
+    EXPECT_EQ((In6Addr{}), fs("::"sv));
+    EXPECT_EQ((In6Addr{255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
+                       255, 255, 255, 255, 255}),
+              fs("ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff"sv));
+    EXPECT_EQ((In6Addr{}), fs("0:0:0:0:0:0:0:0"sv));
+    EXPECT_EQ((In6Addr{0, 0xff}), fs("ff::"sv));
+    EXPECT_EQ((In6Addr{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0xff}),
+              fs("::ff"sv));
+    EXPECT_EQ((In6Addr{0, 0, 0, 0, 0, 0xff, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0xff}),
+              fs("0:0:ff::ff"sv));
+    EXPECT_EQ((In6Addr{0, 1, 0, 2, 0, 3, 0, 4, 0, 0, 0, 6, 0, 7, 0, 8}),
+              fs("1:2:3:4::6:7:8"sv));
+    EXPECT_EQ((In6Addr{0, 1, 0, 2, 0, 3, 0, 4, 0, 5, 0, 6, 0, 7, 0, 0}),
+              fs("1:2:3:4:5:6:7::"sv));
+    EXPECT_EQ((In6Addr{0, 0, 0, 2, 0, 3, 0, 4, 0, 5, 0, 6, 0, 7, 0, 8}),
+              fs("::2:3:4:5:6:7:8"sv));
+    EXPECT_EQ(
+        (In6Addr{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0xff, 0xff, 192, 168, 0, 1}),
+        fs("::ffff:192.168.0.1"sv));
+    EXPECT_EQ((In6Addr{0, 0xff, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 255, 168, 0, 1}),
+              fs("ff::255.168.0.1"sv));
+    EXPECT_EQ((In6Addr{0, 0, 0, 1, 0, 2, 0, 3, 0, 4, 0, 5, 255, 168, 0, 1}),
+              fs("0:1:2:3:4:5:255.168.0.1"sv));
+}
+
 TEST(EqualOperator, InAnyAddr)
 {
     EXPECT_EQ(InAnyAddr(In6Addr{0xff, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,