net/addr/subnet: Add netmask functions

Change-Id: I1319e02655796a842769c2f01869b21d0a2151f4
Signed-off-by: William A. Kennington III <wak@google.com>
diff --git a/include/stdplus/net/addr/subnet.hpp b/include/stdplus/net/addr/subnet.hpp
index 8a783ff..5bcc17d 100644
--- a/include/stdplus/net/addr/subnet.hpp
+++ b/include/stdplus/net/addr/subnet.hpp
@@ -5,6 +5,7 @@
 #include <stdplus/numeric/str.hpp>
 #include <stdplus/str/conv.hpp>
 
+#include <bit>
 #include <limits>
 #include <type_traits>
 
@@ -123,6 +124,25 @@
 
 } // namespace detail
 
+template <typename T>
+constexpr T pfxToMask(std::uint8_t pfx);
+
+template <>
+constexpr In4Addr pfxToMask<In4Addr>(std::uint8_t pfx)
+{
+    return in_addr{detail::addr32Mask(pfx)};
+}
+
+constexpr std::uint8_t maskToPfx(In4Addr mask)
+{
+    uint32_t x = ntoh(mask.s4_addr32);
+    if ((~x & (~x + 1)) != 0)
+    {
+        throw std::invalid_argument("Invalid netmask");
+    }
+    return 32 - std::countr_zero(x);
+}
+
 using Subnet4 = detail::Subnet46<In4Addr, std::uint8_t>;
 using Subnet6 = detail::Subnet46<In6Addr, std::uint8_t>;
 
diff --git a/test/net/addr/subnet.cpp b/test/net/addr/subnet.cpp
index 319a514..ca2fa20 100644
--- a/test/net/addr/subnet.cpp
+++ b/test/net/addr/subnet.cpp
@@ -224,4 +224,15 @@
               fmt::format("a {} b", SubnetAny(In4Addr{1, 2, 3, 4}, 32)));
 }
 
+TEST(Ops, MaskToPfx)
+{
+    EXPECT_EQ(32, maskToPfx(In4Addr{0xff, 0xff, 0xff, 0xff}));
+    EXPECT_EQ(28, maskToPfx(In4Addr{0xff, 0xff, 0xff, 0xf0}));
+    EXPECT_EQ(16, maskToPfx(In4Addr{0xff, 0xff, 0, 0}));
+    EXPECT_EQ(0, maskToPfx(In4Addr{}));
+
+    EXPECT_THROW(maskToPfx(In4Addr{0, 0x8}), std::invalid_argument);
+    EXPECT_THROW(maskToPfx(In4Addr{0x40}), std::invalid_argument);
+}
+
 } // namespace stdplus