types: Add a type for interface addresses

This will be used to uniquely identify them.

Change-Id: Iabd43520ae5060e4f5dfe18e549f96f6b910b3c1
Signed-off-by: William A. Kennington III <wak@google.com>
diff --git a/src/types.cpp b/src/types.cpp
index 55c6d10..37eb69e 100644
--- a/src/types.cpp
+++ b/src/types.cpp
@@ -1,8 +1,18 @@
 #include "types.hpp"
 
+#include <fmt/format.h>
+
 #include <charconv>
 
-namespace phosphor::network::detail
+namespace phosphor::network
+{
+
+void IfAddr::invalidPfx(uint8_t pfx)
+{
+    throw std::invalid_argument(fmt::format("Invalid prefix {}", pfx));
+}
+
+namespace detail
 {
 
 std::string_view AddrBufMaker<ether_addr>::operator()(ether_addr val) noexcept
@@ -91,7 +101,8 @@
     return {buf.data(), ptr};
 }
 
-} // namespace phosphor::network::detail
+} // namespace detail
+} // namespace phosphor::network
 
 std::size_t std::hash<in_addr>::operator()(in_addr addr) const noexcept
 {
@@ -104,6 +115,12 @@
                                          addr.s6_addr32[2], addr.s6_addr32[3]);
 }
 
+std::size_t std::hash<phosphor::network::IfAddr>::operator()(
+    phosphor::network::IfAddr addr) const noexcept
+{
+    return phosphor::network::hash_multi(addr.getAddr(), addr.getPfx());
+}
+
 std::string std::to_string(ether_addr value)
 {
     return string(phosphor::network::detail::AddrBufMaker<ether_addr>{}(value));
@@ -120,3 +137,8 @@
 {
     return std::visit([](auto v) { return std::to_string(v); }, value);
 }
+
+std::string std::to_string(phosphor::network::IfAddr value)
+{
+    return fmt::to_string(value);
+}
diff --git a/src/types.hpp b/src/types.hpp
index 75db6a4..f1f2179 100644
--- a/src/types.hpp
+++ b/src/types.hpp
@@ -32,6 +32,46 @@
 
 // Byte representations for common address types in network byte order
 using InAddrAny = std::variant<in_addr, in6_addr>;
+class IfAddr
+{
+  private:
+    InAddrAny addr;
+    uint8_t pfx;
+
+    static void invalidPfx(uint8_t pfx);
+
+  public:
+    constexpr IfAddr() : addr({}), pfx(0)
+    {
+    }
+
+    constexpr IfAddr(InAddrAny addr, uint8_t pfx) : addr(addr), pfx(pfx)
+    {
+        std::visit(
+            [pfx](auto v) {
+                if (sizeof(v) * 8 < pfx)
+                {
+                    invalidPfx(pfx);
+                }
+            },
+            addr);
+    }
+
+    constexpr auto getAddr() const
+    {
+        return addr;
+    }
+
+    constexpr auto getPfx() const
+    {
+        return pfx;
+    }
+
+    constexpr bool operator==(phosphor::network::IfAddr rhs) const noexcept
+    {
+        return addr == rhs.addr && pfx == rhs.pfx;
+    }
+};
 
 using Timer = sdeventplus::utility::Timer<sdeventplus::ClockId::Monotonic>;
 
@@ -254,6 +294,12 @@
     std::size_t operator()(in6_addr addr) const noexcept;
 };
 
+template <>
+struct std::hash<phosphor::network::IfAddr>
+{
+    std::size_t operator()(phosphor::network::IfAddr addr) const noexcept;
+};
+
 namespace fmt
 {
 template <>
@@ -299,6 +345,29 @@
             v);
     }
 };
+template <>
+struct formatter<phosphor::network::IfAddr>
+{
+  private:
+    fmt::formatter<phosphor::network::InAddrAny> addrF;
+    fmt::formatter<char> strF;
+    fmt::formatter<uint8_t> numF;
+
+  public:
+    template <typename ParseContext>
+    constexpr auto parse(ParseContext& ctx)
+    {
+        return ctx.begin();
+    }
+
+    template <typename FormatContext>
+    auto format(auto v, FormatContext& ctx) const
+    {
+        addrF.format(v.getAddr(), ctx);
+        strF.format('/', ctx);
+        return numF.format(v.getPfx(), ctx);
+    }
+};
 } // namespace fmt
 
 namespace std
@@ -307,6 +376,7 @@
 string to_string(in_addr value);
 string to_string(in6_addr value);
 string to_string(phosphor::network::InAddrAny value);
+string to_string(phosphor::network::IfAddr value);
 } // namespace std
 
 constexpr bool operator==(ether_addr lhs, ether_addr rhs) noexcept
@@ -357,3 +427,8 @@
                },
                v);
 }
+
+auto& operator<<(auto& os, phosphor::network::IfAddr v)
+{
+    return os << v.getAddr() << "/" << std::dec << int{v.getPfx()};
+}
diff --git a/test/test_types.cpp b/test/test_types.cpp
index 8150555..4acf409 100644
--- a/test/test_types.cpp
+++ b/test/test_types.cpp
@@ -111,12 +111,14 @@
     EXPECT_EQ("a 0.0.0.1", fmt::format("a {}", InAddrAny{in_addr{htonl(1)}}));
     EXPECT_EQ("a 100::", fmt::format("a {}", in6_addr{1}));
     EXPECT_EQ("a 100::", fmt::format("a {}", InAddrAny{in6_addr{1}}));
+    EXPECT_EQ("a 100::/90", fmt::format("a {}", IfAddr{in6_addr{1}, 90}));
 
     EXPECT_EQ("01:00:00:00:00:00", std::to_string(ether_addr{1}));
     EXPECT_EQ("0.0.0.1", std::to_string(in_addr{htonl(1)}));
     EXPECT_EQ("0.0.0.1", std::to_string(InAddrAny{in_addr{htonl(1)}}));
     EXPECT_EQ("100::", std::to_string(in6_addr{1}));
     EXPECT_EQ("100::", std::to_string(InAddrAny{in6_addr{1}}));
+    EXPECT_EQ("100::/22", std::to_string(IfAddr{in6_addr{1}, 22}));
 
     EXPECT_EQ("a01:00:00:00:00:00",
               (std::stringstream{} << "a" << ether_addr{1}).str());
@@ -128,6 +130,15 @@
     EXPECT_EQ("a100::", (std::stringstream{} << "a" << in6_addr{1}).str());
     EXPECT_EQ("a100::",
               (std::stringstream{} << "a" << InAddrAny{in6_addr{1}}).str());
+    auto ss = std::stringstream{};
+    constexpr auto addr = IfAddr{in6_addr{1}, 30};
+    ss << "a" << addr;
+    EXPECT_EQ("a100::/30", ss.str());
+
+    EXPECT_NO_THROW(IfAddr(in6_addr{}, 128));
+    EXPECT_NO_THROW(IfAddr(in_addr{}, 32));
+    EXPECT_THROW(IfAddr(in6_addr{}, 129), std::invalid_argument);
+    EXPECT_THROW(IfAddr(in_addr{}, 33), std::invalid_argument);
 }
 
 TEST(Perf, In6Addr)