net/addr/subnet: Add class for representing a network subnet

This makes it possible to represent and IPv4/IPv6 CIDR network segment
via a class. Provides useful functions to compute base address and
determine if addresses are inside the subnet.

Change-Id: Ib9d01e28b6c8a28ccb622fef87b217fc96daf905
Signed-off-by: William A. Kennington III <wak@google.com>
diff --git a/include/meson.build b/include/meson.build
index 1402818..d7cf82b 100644
--- a/include/meson.build
+++ b/include/meson.build
@@ -11,6 +11,7 @@
   'stdplus/hash/tuple.hpp',
   'stdplus/net/addr/ether.hpp',
   'stdplus/net/addr/ip.hpp',
+  'stdplus/net/addr/subnet.hpp',
   'stdplus/numeric/endian.hpp',
   'stdplus/pinned.hpp',
   'stdplus/raw.hpp',
diff --git a/include/stdplus/net/addr/subnet.hpp b/include/stdplus/net/addr/subnet.hpp
new file mode 100644
index 0000000..862237b
--- /dev/null
+++ b/include/stdplus/net/addr/subnet.hpp
@@ -0,0 +1,194 @@
+#include <stdplus/net/addr/ip.hpp>
+#include <stdplus/numeric/endian.hpp>
+
+#include <limits>
+#include <type_traits>
+
+namespace stdplus
+{
+namespace detail
+{
+
+// AddressSan doesn't understand our masking of shift UB
+__attribute__((no_sanitize("undefined"))) constexpr uint32_t
+    addr32Mask(std::ptrdiff_t pfx) noexcept
+{
+    // Positive prefix check + mask to handle UB when the left shift becomes
+    // more than 31 bits
+    return hton(static_cast<uint32_t>(-int32_t{pfx > 0}) & ~uint32_t{0}
+                                                               << (32 - pfx));
+}
+
+constexpr In4Addr addrToSubnet(In4Addr a, std::size_t pfx) noexcept
+{
+    return In4Addr{in_addr{a.s4_addr32 & addr32Mask(pfx)}};
+}
+
+constexpr In6Addr addrToSubnet(In6Addr a, std::size_t pfx, std::size_t i = 0,
+                               std::size_t s = 0, In6Addr ret = {}) noexcept
+{
+    if (s + 32 < pfx)
+    {
+        ret.s6_addr32[i] = a.s6_addr32[i];
+        return addrToSubnet(a, pfx, i + 1, s + 32, ret);
+    }
+    ret.s6_addr32[i] = a.s6_addr32[i] & addr32Mask(pfx - s);
+    return ret;
+}
+
+constexpr InAnyAddr addrToSubnet(InAnyAddr a, std::size_t pfx) noexcept
+{
+    return std::visit([&](auto av) { return InAnyAddr{addrToSubnet(av, pfx)}; },
+                      a);
+}
+
+constexpr bool subnetContains(auto, auto, std::size_t) noexcept
+{
+    return false;
+}
+
+template <typename T>
+constexpr bool subnetContains(T l, T r, std::size_t pfx) noexcept
+{
+    return addrToSubnet(l, pfx) == addrToSubnet(r, pfx);
+}
+
+constexpr bool subnetContains(InAnyAddr l, auto r, std::size_t pfx) noexcept
+{
+    return std::visit([&](auto v) { return detail::subnetContains(v, r, pfx); },
+                      l);
+}
+
+constexpr std::size_t addrBits(auto a) noexcept
+{
+    return sizeof(a) << 3;
+}
+
+void invalidSubnetPfx(std::size_t pfx);
+
+template <typename Addr, typename Pfx>
+class Subnet46
+{
+  private:
+    static constexpr inline std::size_t maxPfx = sizeof(Addr) * 8;
+    static_assert(std::is_unsigned_v<Pfx> && std::is_integral_v<Pfx>);
+    static_assert(std::numeric_limits<Pfx>::max() >= maxPfx);
+
+    Addr addr;
+    Pfx pfx;
+
+  public:
+    constexpr Subnet46(Addr addr, Pfx pfx) : addr(addr), pfx(pfx)
+    {
+        if (addrBits(addr) < pfx)
+        {
+            invalidSubnetPfx(pfx);
+        }
+    }
+
+    constexpr auto getAddr() const noexcept
+    {
+        return addr;
+    }
+
+    constexpr auto getPfx() const noexcept
+    {
+        return pfx;
+    }
+
+    constexpr bool operator==(Subnet46 rhs) const noexcept
+    {
+        return addr == rhs.addr && pfx == rhs.pfx;
+    }
+
+    constexpr Addr network() const noexcept
+    {
+        return addrToSubnet(addr, pfx);
+    }
+
+    constexpr bool contains(Addr addr) const noexcept
+    {
+        return addrToSubnet(this->addr, pfx) == addrToSubnet(addr, pfx);
+    }
+};
+
+} // namespace detail
+
+using Subnet4 = detail::Subnet46<In4Addr, uint8_t>;
+using Subnet6 = detail::Subnet46<In6Addr, uint8_t>;
+
+class SubnetAny
+{
+  private:
+    InAnyAddr addr;
+    uint8_t pfx;
+
+  public:
+    constexpr SubnetAny(auto addr, uint8_t pfx) : addr(addr), pfx(pfx)
+    {
+        if (detail::addrBits(addr) < pfx)
+        {
+            detail::invalidSubnetPfx(pfx);
+        }
+    }
+    constexpr SubnetAny(InAnyAddr addr, uint8_t pfx) : addr(addr), pfx(pfx)
+    {
+        if (std::visit([](auto v) { return detail::addrBits(v); }, addr) < pfx)
+        {
+            detail::invalidSubnetPfx(pfx);
+        }
+    }
+
+    template <typename T, typename S>
+    constexpr SubnetAny(detail::Subnet46<T, S> o) noexcept :
+        addr(o.getAddr()), pfx(o.getPfx())
+    {}
+
+    constexpr auto getAddr() const noexcept
+    {
+        return addr;
+    }
+
+    constexpr auto getPfx() const noexcept
+    {
+        return pfx;
+    }
+
+    template <typename T, typename S>
+    constexpr bool operator==(detail::Subnet46<T, S> rhs) const noexcept
+    {
+        return addr == rhs.getAddr() && pfx == rhs.getPfx();
+    }
+    constexpr bool operator==(SubnetAny rhs) const noexcept
+    {
+        return addr == rhs.addr && pfx == rhs.pfx;
+    }
+
+    constexpr InAnyAddr network() const noexcept
+    {
+        return detail::addrToSubnet(addr, pfx);
+    }
+
+    constexpr bool contains(In4Addr addr) const noexcept
+    {
+        return detail::subnetContains(this->addr, addr, pfx);
+    }
+    constexpr bool contains(in_addr addr) const noexcept
+    {
+        return contains(In4Addr{addr});
+    }
+    constexpr bool contains(In6Addr addr) const noexcept
+    {
+        return detail::subnetContains(this->addr, addr, pfx);
+    }
+    constexpr bool contains(in6_addr addr) const noexcept
+    {
+        return contains(In6Addr{addr});
+    }
+    constexpr bool contains(InAnyAddr addr) const noexcept
+    {
+        return std::visit([&](auto v) { return contains(v); }, addr);
+    }
+};
+
+} // namespace stdplus