blob: cbdce2015684a2cea552ae5f23f5b17d62dc8312 [file] [log] [blame]
#include <fmt/core.h>
#include <stdplus/net/addr/ip.hpp>
#include <stdplus/numeric/endian.hpp>
#include <stdplus/numeric/str.hpp>
#include <stdplus/str/conv.hpp>
#include <bit>
#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
{
// NOLINTNEXTLINE(clang-analyzer-core.UndefinedBinaryOperatorResult)
auto v = ~uint32_t{0} << (32 - pfx);
// 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}) & v);
}
constexpr In4Addr addrToSubnet(In4Addr a, std::size_t pfx) noexcept
{
return In4Addr{in_addr{a.word() & 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.word(i, a.word(i));
return addrToSubnet(a, pfx, i + 1, s + 32, ret);
}
ret.word(i, a.word(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
{
public:
using Addr = Addr_;
using Pfx = Pfx_;
private:
static inline constexpr 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
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.word());
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>;
class SubnetAny;
template <typename T>
struct IsSubnet : std::false_type
{};
template <typename Addr, typename Pfx>
struct IsSubnet<detail::Subnet46<Addr, Pfx>> : std::true_type
{};
template <>
struct IsSubnet<SubnetAny> : std::true_type
{};
template <typename T>
concept Subnet = IsSubnet<T>::value;
class SubnetAny
{
public:
using Addr = InAnyAddr;
using Pfx = std::uint8_t;
private:
Addr addr;
Pfx pfx;
public:
constexpr SubnetAny(auto addr, Pfx pfx) : addr(addr), pfx(pfx)
{
if (detail::addrBits(addr) < pfx)
{
detail::invalidSubnetPfx(pfx);
}
}
constexpr SubnetAny(InAnyAddr addr, Pfx pfx) : addr(addr), pfx(pfx)
{
if (std::visit([](auto v) { return detail::addrBits(v); }, addr) < pfx)
{
detail::invalidSubnetPfx(pfx);
}
}
template <Subnet T>
constexpr SubnetAny(T o) noexcept : addr(o.getAddr()), pfx(o.getPfx())
{}
constexpr auto getAddr() const noexcept
{
return addr;
}
constexpr auto getPfx() const noexcept
{
return pfx;
}
template <Subnet T>
constexpr bool operator==(T rhs) const noexcept
{
return addr == rhs.getAddr() && pfx == rhs.getPfx();
}
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);
}
};
template <Subnet Sub>
struct FromStr<Sub>
{
template <typename CharT>
constexpr Sub operator()(std::basic_string_view<CharT> sv) const
{
const auto pos = sv.rfind('/');
if (pos == sv.npos)
{
throw std::invalid_argument("Invalid subnet");
}
return {FromStr<typename Sub::Addr>{}(sv.substr(0, pos)),
StrToInt<10, typename Sub::Pfx>{}(sv.substr(pos + 1))};
}
};
template <Subnet Sub>
struct ToStr<Sub>
{
using type = Sub;
using ToDec = IntToStr<10, typename Sub::Pfx>;
// Addr + sep + 3 prefix chars
static inline constexpr std::size_t buf_size =
ToStr<typename Sub::Addr>::buf_size + 1 + ToDec::buf_size;
template <typename CharT>
constexpr CharT* operator()(CharT* buf, Sub v) const noexcept
{
buf = ToStr<typename Sub::Addr>{}(buf, v.getAddr());
(buf++)[0] = '/';
return ToDec{}(buf, v.getPfx());
}
};
namespace detail
{
template <Subnet Sub>
struct CompileInAddrInt<Sub> : CompileInAddrInt<typename Sub::Addr>
{
Sub::Pfx pfx = 0;
template <typename CharT>
consteval void compile(std::basic_string_view<CharT> sv) noexcept
{
const auto pos = sv.rfind('/');
if (pos == sv.npos)
{
this->valid = false;
}
static_cast<CompileInAddrInt<typename Sub::Addr>&>(*this).compile(
sv.substr(0, pos));
try
{
pfx = StrToInt<10, typename Sub::Pfx>{}(sv.substr(pos + 1));
}
catch (...)
{
this->valid = false;
}
}
};
template <typename CharT, std::size_t N>
struct CompileSubnet4 : CompileInAddr<Subnet4, CharT, N>
{
consteval CompileSubnet4(const CharT (&str)[N]) noexcept :
CompileInAddr<Subnet4, CharT, N>(str)
{}
};
template <typename CharT, std::size_t N>
struct CompileSubnet6 : CompileInAddr<Subnet6, CharT, N>
{
consteval CompileSubnet6(const CharT (&str)[N]) noexcept :
CompileInAddr<Subnet6, CharT, N>(str)
{}
};
template <typename CharT, std::size_t N>
struct CompileSubnetAny : CompileInAddr<SubnetAny, CharT, N>
{
consteval CompileSubnetAny(const CharT (&str)[N]) noexcept :
CompileInAddr<SubnetAny, CharT, N>(str)
{}
};
} // namespace detail
inline namespace subnet_literals
{
template <detail::CompileSubnet4 Str>
constexpr auto operator"" _sub4() noexcept
{
static_assert(Str.valid, "stdplus::Subnet4");
return Subnet4(Str.addr, Str.pfx);
}
template <detail::CompileSubnet6 Str>
constexpr auto operator"" _sub6() noexcept
{
static_assert(Str.valid, "stdplus::Subnet6");
return Subnet6(Str.addr, Str.pfx);
}
template <detail::CompileSubnetAny Str>
constexpr auto operator"" _sub() noexcept
{
static_assert(Str.valid, "stdplus::SubnetAny");
return Str.v4 ? SubnetAny(Str.u.addr4, Str.pfx)
: SubnetAny(Str.u.addr6, Str.pfx);
}
} // namespace subnet_literals
} // namespace stdplus
template <stdplus::Subnet Sub, typename CharT>
struct fmt::formatter<Sub, CharT> : stdplus::Format<stdplus::ToStr<Sub>, CharT>
{};
template <stdplus::Subnet Sub>
struct std::hash<Sub>
{
constexpr std::size_t operator()(Sub addr) const noexcept
{
return stdplus::hashMulti(addr.getAddr(), addr.getPfx());
}
};
namespace std
{
template <typename T, typename CharT>
struct formatter;
template <stdplus::Subnet Sub, typename CharT>
struct formatter<Sub, CharT> : stdplus::Format<stdplus::ToStr<Sub>, CharT>
{};
} // namespace std