William A. Kennington III | d5957f5 | 2023-06-16 16:55:01 -0700 | [diff] [blame] | 1 | #include <fmt/core.h> |
| 2 | |
William A. Kennington III | 14dd4eb | 2023-01-12 10:51:12 -0800 | [diff] [blame] | 3 | #include <stdplus/net/addr/ip.hpp> |
| 4 | #include <stdplus/numeric/endian.hpp> |
William A. Kennington III | d5957f5 | 2023-06-16 16:55:01 -0700 | [diff] [blame] | 5 | #include <stdplus/numeric/str.hpp> |
| 6 | #include <stdplus/str/conv.hpp> |
William A. Kennington III | 14dd4eb | 2023-01-12 10:51:12 -0800 | [diff] [blame] | 7 | |
William A. Kennington III | c870ac3 | 2023-06-17 17:13:45 -0700 | [diff] [blame] | 8 | #include <bit> |
William A. Kennington III | 14dd4eb | 2023-01-12 10:51:12 -0800 | [diff] [blame] | 9 | #include <limits> |
| 10 | #include <type_traits> |
| 11 | |
| 12 | namespace stdplus |
| 13 | { |
| 14 | namespace detail |
| 15 | { |
| 16 | |
| 17 | // AddressSan doesn't understand our masking of shift UB |
| 18 | __attribute__((no_sanitize("undefined"))) constexpr uint32_t |
| 19 | addr32Mask(std::ptrdiff_t pfx) noexcept |
| 20 | { |
William A. Kennington III | 49d9269 | 2023-06-06 14:02:01 -0700 | [diff] [blame] | 21 | // NOLINTNEXTLINE(clang-analyzer-core.UndefinedBinaryOperatorResult) |
| 22 | auto v = ~uint32_t{0} << (32 - pfx); |
William A. Kennington III | 14dd4eb | 2023-01-12 10:51:12 -0800 | [diff] [blame] | 23 | // Positive prefix check + mask to handle UB when the left shift becomes |
| 24 | // more than 31 bits |
William A. Kennington III | 49d9269 | 2023-06-06 14:02:01 -0700 | [diff] [blame] | 25 | return hton(static_cast<uint32_t>(-int32_t{pfx > 0}) & v); |
William A. Kennington III | 14dd4eb | 2023-01-12 10:51:12 -0800 | [diff] [blame] | 26 | } |
| 27 | |
| 28 | constexpr In4Addr addrToSubnet(In4Addr a, std::size_t pfx) noexcept |
| 29 | { |
William A. Kennington III | 3bd5add | 2023-06-28 11:56:15 -0700 | [diff] [blame^] | 30 | return In4Addr{in_addr{a.word() & addr32Mask(pfx)}}; |
William A. Kennington III | 14dd4eb | 2023-01-12 10:51:12 -0800 | [diff] [blame] | 31 | } |
| 32 | |
| 33 | constexpr In6Addr addrToSubnet(In6Addr a, std::size_t pfx, std::size_t i = 0, |
| 34 | std::size_t s = 0, In6Addr ret = {}) noexcept |
| 35 | { |
| 36 | if (s + 32 < pfx) |
| 37 | { |
| 38 | ret.s6_addr32[i] = a.s6_addr32[i]; |
| 39 | return addrToSubnet(a, pfx, i + 1, s + 32, ret); |
| 40 | } |
| 41 | ret.s6_addr32[i] = a.s6_addr32[i] & addr32Mask(pfx - s); |
| 42 | return ret; |
| 43 | } |
| 44 | |
| 45 | constexpr InAnyAddr addrToSubnet(InAnyAddr a, std::size_t pfx) noexcept |
| 46 | { |
| 47 | return std::visit([&](auto av) { return InAnyAddr{addrToSubnet(av, pfx)}; }, |
| 48 | a); |
| 49 | } |
| 50 | |
| 51 | constexpr bool subnetContains(auto, auto, std::size_t) noexcept |
| 52 | { |
| 53 | return false; |
| 54 | } |
| 55 | |
| 56 | template <typename T> |
| 57 | constexpr bool subnetContains(T l, T r, std::size_t pfx) noexcept |
| 58 | { |
| 59 | return addrToSubnet(l, pfx) == addrToSubnet(r, pfx); |
| 60 | } |
| 61 | |
| 62 | constexpr bool subnetContains(InAnyAddr l, auto r, std::size_t pfx) noexcept |
| 63 | { |
| 64 | return std::visit([&](auto v) { return detail::subnetContains(v, r, pfx); }, |
| 65 | l); |
| 66 | } |
| 67 | |
| 68 | constexpr std::size_t addrBits(auto a) noexcept |
| 69 | { |
| 70 | return sizeof(a) << 3; |
| 71 | } |
| 72 | |
| 73 | void invalidSubnetPfx(std::size_t pfx); |
| 74 | |
William A. Kennington III | d5957f5 | 2023-06-16 16:55:01 -0700 | [diff] [blame] | 75 | template <typename Addr_, typename Pfx_> |
William A. Kennington III | 14dd4eb | 2023-01-12 10:51:12 -0800 | [diff] [blame] | 76 | class Subnet46 |
| 77 | { |
William A. Kennington III | d5957f5 | 2023-06-16 16:55:01 -0700 | [diff] [blame] | 78 | public: |
| 79 | using Addr = Addr_; |
| 80 | using Pfx = Pfx_; |
| 81 | |
William A. Kennington III | 14dd4eb | 2023-01-12 10:51:12 -0800 | [diff] [blame] | 82 | private: |
| 83 | static constexpr inline std::size_t maxPfx = sizeof(Addr) * 8; |
| 84 | static_assert(std::is_unsigned_v<Pfx> && std::is_integral_v<Pfx>); |
| 85 | static_assert(std::numeric_limits<Pfx>::max() >= maxPfx); |
| 86 | |
| 87 | Addr addr; |
| 88 | Pfx pfx; |
| 89 | |
| 90 | public: |
| 91 | constexpr Subnet46(Addr addr, Pfx pfx) : addr(addr), pfx(pfx) |
| 92 | { |
| 93 | if (addrBits(addr) < pfx) |
| 94 | { |
| 95 | invalidSubnetPfx(pfx); |
| 96 | } |
| 97 | } |
| 98 | |
| 99 | constexpr auto getAddr() const noexcept |
| 100 | { |
| 101 | return addr; |
| 102 | } |
| 103 | |
| 104 | constexpr auto getPfx() const noexcept |
| 105 | { |
| 106 | return pfx; |
| 107 | } |
| 108 | |
| 109 | constexpr bool operator==(Subnet46 rhs) const noexcept |
| 110 | { |
| 111 | return addr == rhs.addr && pfx == rhs.pfx; |
| 112 | } |
| 113 | |
| 114 | constexpr Addr network() const noexcept |
| 115 | { |
| 116 | return addrToSubnet(addr, pfx); |
| 117 | } |
| 118 | |
| 119 | constexpr bool contains(Addr addr) const noexcept |
| 120 | { |
| 121 | return addrToSubnet(this->addr, pfx) == addrToSubnet(addr, pfx); |
| 122 | } |
| 123 | }; |
| 124 | |
| 125 | } // namespace detail |
| 126 | |
William A. Kennington III | c870ac3 | 2023-06-17 17:13:45 -0700 | [diff] [blame] | 127 | template <typename T> |
| 128 | constexpr T pfxToMask(std::uint8_t pfx); |
| 129 | |
| 130 | template <> |
| 131 | constexpr In4Addr pfxToMask<In4Addr>(std::uint8_t pfx) |
| 132 | { |
| 133 | return in_addr{detail::addr32Mask(pfx)}; |
| 134 | } |
| 135 | |
| 136 | constexpr std::uint8_t maskToPfx(In4Addr mask) |
| 137 | { |
William A. Kennington III | 3bd5add | 2023-06-28 11:56:15 -0700 | [diff] [blame^] | 138 | uint32_t x = ntoh(mask.word()); |
William A. Kennington III | c870ac3 | 2023-06-17 17:13:45 -0700 | [diff] [blame] | 139 | if ((~x & (~x + 1)) != 0) |
| 140 | { |
| 141 | throw std::invalid_argument("Invalid netmask"); |
| 142 | } |
| 143 | return 32 - std::countr_zero(x); |
| 144 | } |
| 145 | |
William A. Kennington III | d5957f5 | 2023-06-16 16:55:01 -0700 | [diff] [blame] | 146 | using Subnet4 = detail::Subnet46<In4Addr, std::uint8_t>; |
| 147 | using Subnet6 = detail::Subnet46<In6Addr, std::uint8_t>; |
William A. Kennington III | 14dd4eb | 2023-01-12 10:51:12 -0800 | [diff] [blame] | 148 | |
| 149 | class SubnetAny |
| 150 | { |
William A. Kennington III | d5957f5 | 2023-06-16 16:55:01 -0700 | [diff] [blame] | 151 | public: |
| 152 | using Addr = InAnyAddr; |
| 153 | using Pfx = std::uint8_t; |
| 154 | |
William A. Kennington III | 14dd4eb | 2023-01-12 10:51:12 -0800 | [diff] [blame] | 155 | private: |
William A. Kennington III | d5957f5 | 2023-06-16 16:55:01 -0700 | [diff] [blame] | 156 | Addr addr; |
| 157 | Pfx pfx; |
William A. Kennington III | 14dd4eb | 2023-01-12 10:51:12 -0800 | [diff] [blame] | 158 | |
| 159 | public: |
William A. Kennington III | d5957f5 | 2023-06-16 16:55:01 -0700 | [diff] [blame] | 160 | constexpr SubnetAny(auto addr, Pfx pfx) : addr(addr), pfx(pfx) |
William A. Kennington III | 14dd4eb | 2023-01-12 10:51:12 -0800 | [diff] [blame] | 161 | { |
| 162 | if (detail::addrBits(addr) < pfx) |
| 163 | { |
| 164 | detail::invalidSubnetPfx(pfx); |
| 165 | } |
| 166 | } |
William A. Kennington III | d5957f5 | 2023-06-16 16:55:01 -0700 | [diff] [blame] | 167 | constexpr SubnetAny(InAnyAddr addr, Pfx pfx) : addr(addr), pfx(pfx) |
William A. Kennington III | 14dd4eb | 2023-01-12 10:51:12 -0800 | [diff] [blame] | 168 | { |
| 169 | if (std::visit([](auto v) { return detail::addrBits(v); }, addr) < pfx) |
| 170 | { |
| 171 | detail::invalidSubnetPfx(pfx); |
| 172 | } |
| 173 | } |
| 174 | |
| 175 | template <typename T, typename S> |
| 176 | constexpr SubnetAny(detail::Subnet46<T, S> o) noexcept : |
| 177 | addr(o.getAddr()), pfx(o.getPfx()) |
| 178 | {} |
| 179 | |
| 180 | constexpr auto getAddr() const noexcept |
| 181 | { |
| 182 | return addr; |
| 183 | } |
| 184 | |
| 185 | constexpr auto getPfx() const noexcept |
| 186 | { |
| 187 | return pfx; |
| 188 | } |
| 189 | |
| 190 | template <typename T, typename S> |
| 191 | constexpr bool operator==(detail::Subnet46<T, S> rhs) const noexcept |
| 192 | { |
| 193 | return addr == rhs.getAddr() && pfx == rhs.getPfx(); |
| 194 | } |
| 195 | constexpr bool operator==(SubnetAny rhs) const noexcept |
| 196 | { |
| 197 | return addr == rhs.addr && pfx == rhs.pfx; |
| 198 | } |
| 199 | |
| 200 | constexpr InAnyAddr network() const noexcept |
| 201 | { |
| 202 | return detail::addrToSubnet(addr, pfx); |
| 203 | } |
| 204 | |
| 205 | constexpr bool contains(In4Addr addr) const noexcept |
| 206 | { |
| 207 | return detail::subnetContains(this->addr, addr, pfx); |
| 208 | } |
| 209 | constexpr bool contains(in_addr addr) const noexcept |
| 210 | { |
| 211 | return contains(In4Addr{addr}); |
| 212 | } |
| 213 | constexpr bool contains(In6Addr addr) const noexcept |
| 214 | { |
| 215 | return detail::subnetContains(this->addr, addr, pfx); |
| 216 | } |
| 217 | constexpr bool contains(in6_addr addr) const noexcept |
| 218 | { |
| 219 | return contains(In6Addr{addr}); |
| 220 | } |
| 221 | constexpr bool contains(InAnyAddr addr) const noexcept |
| 222 | { |
| 223 | return std::visit([&](auto v) { return contains(v); }, addr); |
| 224 | } |
| 225 | }; |
| 226 | |
William A. Kennington III | d5957f5 | 2023-06-16 16:55:01 -0700 | [diff] [blame] | 227 | namespace detail |
| 228 | { |
| 229 | |
| 230 | template <typename Subnet> |
William A. Kennington III | 160e382 | 2023-06-16 17:09:43 -0700 | [diff] [blame] | 231 | struct SubnetFromStr |
| 232 | { |
| 233 | template <typename CharT> |
| 234 | constexpr Subnet operator()(std::basic_string_view<CharT> sv) const |
| 235 | { |
| 236 | const auto pos = sv.rfind('/'); |
| 237 | if (pos == sv.npos) |
| 238 | { |
| 239 | throw std::invalid_argument("Invalid subnet"); |
| 240 | } |
| 241 | return {FromStr<typename Subnet::Addr>{}(sv.substr(0, pos)), |
| 242 | StrToInt<10, typename Subnet::Pfx>{}(sv.substr(pos + 1))}; |
| 243 | } |
| 244 | }; |
| 245 | |
| 246 | template <typename Subnet> |
William A. Kennington III | d5957f5 | 2023-06-16 16:55:01 -0700 | [diff] [blame] | 247 | struct SubnetToStr |
| 248 | { |
| 249 | using type = Subnet; |
| 250 | using ToDec = IntToStr<10, typename Subnet::Pfx>; |
| 251 | // Addr + sep + 3 prefix chars |
| 252 | static constexpr std::size_t buf_size = |
| 253 | ToStr<typename Subnet::Addr>::buf_size + 1 + ToDec::buf_size; |
| 254 | |
| 255 | template <typename CharT> |
| 256 | constexpr CharT* operator()(CharT* buf, Subnet v) const noexcept |
| 257 | { |
| 258 | buf = ToStr<typename Subnet::Addr>{}(buf, v.getAddr()); |
| 259 | (buf++)[0] = '/'; |
| 260 | return ToDec{}(buf, v.getPfx()); |
| 261 | } |
| 262 | }; |
| 263 | |
William A. Kennington III | b03c9cb | 2023-06-17 16:36:21 -0700 | [diff] [blame] | 264 | template <typename Subnet> |
| 265 | struct SubnetHash |
| 266 | { |
| 267 | constexpr std::size_t operator()(Subnet addr) const noexcept |
| 268 | { |
| 269 | return stdplus::hashMulti(addr.getAddr(), addr.getPfx()); |
| 270 | } |
| 271 | }; |
| 272 | |
William A. Kennington III | d5957f5 | 2023-06-16 16:55:01 -0700 | [diff] [blame] | 273 | } // namespace detail |
| 274 | |
| 275 | template <typename Addr, typename Pfx> |
William A. Kennington III | 160e382 | 2023-06-16 17:09:43 -0700 | [diff] [blame] | 276 | struct FromStr<detail::Subnet46<Addr, Pfx>> : |
| 277 | detail::SubnetFromStr<detail::Subnet46<Addr, Pfx>> |
| 278 | {}; |
| 279 | |
| 280 | template <> |
| 281 | struct FromStr<SubnetAny> : detail::SubnetFromStr<SubnetAny> |
| 282 | {}; |
| 283 | |
| 284 | template <typename Addr, typename Pfx> |
William A. Kennington III | d5957f5 | 2023-06-16 16:55:01 -0700 | [diff] [blame] | 285 | struct ToStr<detail::Subnet46<Addr, Pfx>> : |
| 286 | detail::SubnetToStr<detail::Subnet46<Addr, Pfx>> |
| 287 | {}; |
| 288 | |
| 289 | template <> |
| 290 | struct ToStr<SubnetAny> : detail::SubnetToStr<SubnetAny> |
| 291 | {}; |
| 292 | |
William A. Kennington III | 14dd4eb | 2023-01-12 10:51:12 -0800 | [diff] [blame] | 293 | } // namespace stdplus |
William A. Kennington III | d5957f5 | 2023-06-16 16:55:01 -0700 | [diff] [blame] | 294 | |
| 295 | template <typename Addr, typename Pfx, typename CharT> |
| 296 | struct fmt::formatter<stdplus::detail::Subnet46<Addr, Pfx>, CharT> : |
| 297 | stdplus::Format<stdplus::ToStr<stdplus::detail::Subnet46<Addr, Pfx>>, CharT> |
| 298 | {}; |
| 299 | |
| 300 | template <typename CharT> |
| 301 | struct fmt::formatter<stdplus::SubnetAny, CharT> : |
| 302 | stdplus::Format<stdplus::ToStr<stdplus::SubnetAny>, CharT> |
| 303 | {}; |
William A. Kennington III | b03c9cb | 2023-06-17 16:36:21 -0700 | [diff] [blame] | 304 | |
| 305 | template <typename Addr, typename Pfx> |
| 306 | struct std::hash<stdplus::detail::Subnet46<Addr, Pfx>> : |
| 307 | stdplus::detail::SubnetHash<stdplus::detail::Subnet46<Addr, Pfx>> |
| 308 | {}; |
| 309 | |
| 310 | template <> |
| 311 | struct std::hash<stdplus::SubnetAny> : |
| 312 | stdplus::detail::SubnetHash<stdplus::SubnetAny> |
| 313 | {}; |