net/addr/ip: Add In4Addr ToStr conversion

Change-Id: Id2f16834c02a2bc500aae45dc852113cee1eff87
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 0f5b30f..59a8f2f 100644
--- a/include/stdplus/net/addr/ip.hpp
+++ b/include/stdplus/net/addr/ip.hpp
@@ -77,6 +77,28 @@
     }
 };
 
+template <>
+struct ToStr<In4Addr>
+{
+    using type = In4Addr;
+    using ToOct = IntToStr<10, uint8_t>;
+    // 4 octets * 3 dec chars + 3 separators
+    static constexpr std::size_t buf_size = 12 + ToOct::buf_size;
+
+    template <typename CharT>
+    constexpr CharT* operator()(CharT* buf, In4Addr v) const noexcept
+    {
+        auto n = bswap(ntoh(v.s4_addr32));
+        for (size_t i = 0; i < 3; ++i)
+        {
+            buf = ToOct{}(buf, n & 0xff);
+            (buf++)[0] = '.';
+            n >>= 8;
+        }
+        return ToOct{}(buf, n & 0xff);
+    }
+};
+
 struct In6Addr : in6_addr
 {
     constexpr In6Addr() noexcept : in6_addr() {}
@@ -144,3 +166,8 @@
         return std::hash<stdplus::detail::InAnyAddrV>{}(a);
     }
 };
+
+template <typename CharT>
+struct fmt::formatter<stdplus::In4Addr, CharT> :
+    stdplus::Format<stdplus::ToStr<stdplus::In4Addr>, CharT>
+{};
diff --git a/src/net/addr/ip.cpp b/src/net/addr/ip.cpp
index 81d2a4a..fca752a 100644
--- a/src/net/addr/ip.cpp
+++ b/src/net/addr/ip.cpp
@@ -3,4 +3,5 @@
 namespace stdplus
 {
 template In4Addr FromStr<In4Addr>::operator()(std::string_view) const;
-}
+template char* ToStr<In4Addr>::operator()(char*, In4Addr) const noexcept;
+} // namespace stdplus
diff --git a/test/net/addr/ip.cpp b/test/net/addr/ip.cpp
index 625633e..0a2bc30 100644
--- a/test/net/addr/ip.cpp
+++ b/test/net/addr/ip.cpp
@@ -1,3 +1,5 @@
+#include <fmt/format.h>
+
 #include <stdplus/net/addr/ip.hpp>
 #include <stdplus/numeric/endian.hpp>
 
@@ -30,6 +32,15 @@
     EXPECT_EQ((In4Addr{192, 168, 1, 1}), fromStr<In4Addr>("192.168.001.1"));
 }
 
+TEST(ToStr, In4Addr)
+{
+    ToStrHandle<ToStr<In4Addr>> tsh;
+    EXPECT_EQ("255.255.255.255", tsh(in_addr{0xffffffff}));
+    EXPECT_EQ("1.15.3.4", tsh(In4Addr{1, 15, 3, 4}));
+    EXPECT_EQ("0.0.0.0", tsh(In4Addr{}));
+    EXPECT_EQ("a 1.15.3.4 b", fmt::format("a {} b", In4Addr{1, 15, 3, 4}));
+}
+
 TEST(EqualOperator, In6Addr)
 {
     EXPECT_EQ((In6Addr{0xff, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0xff}),