net/addr/ip: Add IPv6 ToStr conversion

Change-Id: Icab45bc29732eb9dc6cf42532ec74fe1c48afa91
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 b312536..d42605c 100644
--- a/include/stdplus/net/addr/ip.hpp
+++ b/include/stdplus/net/addr/ip.hpp
@@ -181,6 +181,75 @@
     }
 };
 
+template <>
+struct ToStr<In6Addr>
+{
+    using type = In6Addr;
+    using ToHex = IntToStr<16, uint16_t>;
+    // 8 hextets * 4 hex chars + 7 separators
+    static constexpr std::size_t buf_size = 35 + ToHex::buf_size;
+
+    template <typename CharT>
+    constexpr CharT* operator()(CharT* buf, In6Addr v) const noexcept
+    {
+        // IPv4 in IPv6 Addr
+        if (v.s6_addr32[0] == 0 && v.s6_addr32[1] == 0 && v.s6_addr16[4] == 0 &&
+            v.s6_addr16[5] == 0xffff)
+        {
+            constexpr auto prefix = std::string_view("::ffff:");
+            return ToStr<In4Addr>{}(
+                std::copy(prefix.begin(), prefix.end(), buf),
+                in_addr{v.s6_addr32[3]});
+        }
+
+        size_t skip_start = 0;
+        size_t skip_size = 0;
+        {
+            size_t new_start = 0;
+            size_t new_size = 0;
+            for (size_t i = 0; i < 9; ++i)
+            {
+                if (i < 8 && v.s6_addr16[i] == 0)
+                {
+                    if (new_start + new_size == i)
+                    {
+                        new_size++;
+                    }
+                    else
+                    {
+                        new_start = i;
+                        new_size = 1;
+                    }
+                }
+                else if (new_start + new_size == i && new_size > skip_size)
+                {
+                    skip_start = new_start;
+                    skip_size = new_size;
+                }
+            }
+        }
+        for (size_t i = 0; i < 8; ++i)
+        {
+            if (i == skip_start && skip_size > 1)
+            {
+                if (i == 0)
+                {
+                    *(buf++) = ':';
+                }
+                *(buf++) = ':';
+                i += skip_size - 1;
+                continue;
+            }
+            buf = ToHex{}(buf, ntoh(v.s6_addr16[i]));
+            if (i < 7)
+            {
+                *(buf++) = ':';
+            }
+        }
+        return buf;
+    }
+};
+
 namespace detail
 {
 using InAnyAddrV = std::variant<In4Addr, In6Addr>;
@@ -232,3 +301,8 @@
 struct fmt::formatter<stdplus::In4Addr, CharT> :
     stdplus::Format<stdplus::ToStr<stdplus::In4Addr>, CharT>
 {};
+
+template <typename CharT>
+struct fmt::formatter<stdplus::In6Addr, CharT> :
+    stdplus::Format<stdplus::ToStr<stdplus::In6Addr>, CharT>
+{};
diff --git a/src/net/addr/ip.cpp b/src/net/addr/ip.cpp
index 16e328f..e411054 100644
--- a/src/net/addr/ip.cpp
+++ b/src/net/addr/ip.cpp
@@ -5,4 +5,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;
+template char* ToStr<In6Addr>::operator()(char*, In6Addr) const noexcept;
 } // namespace stdplus
diff --git a/test/net/addr/ip.cpp b/test/net/addr/ip.cpp
index 3e02404..7296a1d 100644
--- a/test/net/addr/ip.cpp
+++ b/test/net/addr/ip.cpp
@@ -102,6 +102,36 @@
               fs("0:1:2:3:4:5:255.168.0.1"sv));
 }
 
+TEST(ToStr, In6Addr)
+{
+    ToStrHandle<ToStr<In6Addr>> tsh;
+    EXPECT_EQ("::", tsh(In6Addr{}));
+    EXPECT_EQ("ff::", tsh(In6Addr{0, 0xff}));
+    EXPECT_EQ("::ff",
+              tsh(In6Addr{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0xff}));
+    EXPECT_EQ("0:0:ff::ff", tsh(In6Addr{0, 0, 0, 0, 0, 0xff, 0, 0, 0, 0, 0, 0,
+                                        0, 0, 0, 0xff}));
+    EXPECT_EQ("::100:0:ff",
+              tsh(In6Addr{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0xff}));
+    EXPECT_EQ("ff00::", tsh(In6Addr{0xff}));
+    EXPECT_EQ("1:2:3:4:5:6:7:8",
+              tsh(In6Addr{0, 1, 0, 2, 0, 3, 0, 4, 0, 5, 0, 6, 0, 7, 0, 8}));
+    EXPECT_EQ("ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff",
+              tsh(In6Addr{255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
+                          255, 255, 255, 255, 255}));
+    // rfc5952 4.2.2
+    EXPECT_EQ("1:2:3:4:0:6:7:8",
+              tsh(In6Addr{0, 1, 0, 2, 0, 3, 0, 4, 0, 0, 0, 6, 0, 7, 0, 8}));
+    // rfc5952 4.2.3
+    EXPECT_EQ("1::4:0:0:7:8",
+              tsh(In6Addr{0, 1, 0, 0, 0, 0, 0, 4, 0, 0, 0, 0, 0, 7, 0, 8}));
+    // rfc5952 5
+    EXPECT_EQ("::ffff:192.168.0.1", tsh(In6Addr{0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+                                                0xff, 0xff, 192, 168, 0, 1}));
+
+    EXPECT_EQ("a ff00:: b", fmt::format("a {} b", In6Addr{0xff}));
+}
+
 TEST(EqualOperator, InAnyAddr)
 {
     EXPECT_EQ(InAnyAddr(In6Addr{0xff, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,