net/addr/ether: Add ToStr conversion

Change-Id: Ic2bd81f4e453a53024dd9288f7b3d1e292ad4574
Signed-off-by: William A. Kennington III <wak@google.com>
diff --git a/include/stdplus/net/addr/ether.hpp b/include/stdplus/net/addr/ether.hpp
index 4e77145..a24a319 100644
--- a/include/stdplus/net/addr/ether.hpp
+++ b/include/stdplus/net/addr/ether.hpp
@@ -30,6 +30,30 @@
 };
 
 template <>
+struct ToStr<EtherAddr>
+{
+    using type = EtherAddr;
+    using ToHex = IntToStr<16, uint8_t>;
+    // 6 octets * 2 hex chars + 5 separators
+    static inline constexpr uint8_t buf_size = 15 + ToHex::buf_size;
+
+    template <typename CharT>
+    constexpr CharT* operator()(CharT* buf, EtherAddr v) const noexcept
+    {
+        for (auto ptr = buf + 2; ptr < buf + buf_size; ptr += 3)
+        {
+            *ptr = ':';
+        }
+        auto ptr = buf;
+        for (std::size_t i = 0; i < 6; ++i, ptr += 3)
+        {
+            ToHex{}(ptr, v.ether_addr_octet[i], 2);
+        }
+        return buf + buf_size;
+    }
+};
+
+template <>
 struct FromStr<EtherAddr>
 {
     template <typename CharT>
@@ -72,3 +96,8 @@
         return stdplus::hashMulti(addr.ether_addr_octet);
     }
 };
+
+template <typename CharT>
+struct fmt::formatter<stdplus::EtherAddr, CharT> :
+    stdplus::Format<stdplus::ToStr<stdplus::EtherAddr>, CharT>
+{};
diff --git a/src/net/addr/ether.cpp b/src/net/addr/ether.cpp
index 5266733..646637b 100644
--- a/src/net/addr/ether.cpp
+++ b/src/net/addr/ether.cpp
@@ -2,5 +2,6 @@
 
 namespace stdplus
 {
+template char* ToStr<EtherAddr>::operator()(char*, EtherAddr) const noexcept;
 template EtherAddr FromStr<EtherAddr>::operator()(std::string_view) const;
-}
+} // namespace stdplus
diff --git a/test/net/addr/ether.cpp b/test/net/addr/ether.cpp
index 012e1b0..8005906 100644
--- a/test/net/addr/ether.cpp
+++ b/test/net/addr/ether.cpp
@@ -1,3 +1,5 @@
+#include <fmt/format.h>
+
 #include <stdplus/net/addr/ether.hpp>
 
 #include <gtest/gtest.h>
@@ -13,6 +15,20 @@
     std::hash<EtherAddr>{}(EtherAddr{});
 }
 
+TEST(ToStr, EthAddr)
+{
+    ToStrHandle<ToStr<EtherAddr>> tsh;
+    EXPECT_EQ("11:22:33:44:55:66",
+              tsh(EtherAddr{0x11, 0x22, 0x33, 0x44, 0x55, 0x66}));
+    EXPECT_EQ("01:02:03:04:05:67",
+              tsh(EtherAddr{0x01, 0x02, 0x03, 0x04, 0x05, 0x67}));
+    EXPECT_EQ("00:00:00:00:00:00",
+              tsh(ether_addr{0x00, 0x00, 0x00, 0x00, 0x00, 0x00}));
+    EXPECT_EQ(
+        "a 01:02:03:04:05:67 b",
+        fmt::format("a {} b", EtherAddr{0x01, 0x02, 0x03, 0x04, 0x05, 0x67}));
+}
+
 TEST(FromStr, EtherAddr)
 {
     EXPECT_THROW(fromStr<EtherAddr>("0x:00:00:00:00:00"),