blob: 0647f0752fc97aafa36b558b52e84204f7a4681f [file] [log] [blame]
William A. Kennington IIId5957f52023-06-16 16:55:01 -07001#include <fmt/core.h>
2
William A. Kennington III14dd4eb2023-01-12 10:51:12 -08003#include <stdplus/net/addr/ip.hpp>
4#include <stdplus/numeric/endian.hpp>
William A. Kennington IIId5957f52023-06-16 16:55:01 -07005#include <stdplus/numeric/str.hpp>
6#include <stdplus/str/conv.hpp>
William A. Kennington III14dd4eb2023-01-12 10:51:12 -08007
William A. Kennington IIIc870ac32023-06-17 17:13:45 -07008#include <bit>
William A. Kennington III14dd4eb2023-01-12 10:51:12 -08009#include <limits>
10#include <type_traits>
11
12namespace stdplus
13{
14namespace 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 III49d92692023-06-06 14:02:01 -070021 // NOLINTNEXTLINE(clang-analyzer-core.UndefinedBinaryOperatorResult)
22 auto v = ~uint32_t{0} << (32 - pfx);
William A. Kennington III14dd4eb2023-01-12 10:51:12 -080023 // Positive prefix check + mask to handle UB when the left shift becomes
24 // more than 31 bits
William A. Kennington III49d92692023-06-06 14:02:01 -070025 return hton(static_cast<uint32_t>(-int32_t{pfx > 0}) & v);
William A. Kennington III14dd4eb2023-01-12 10:51:12 -080026}
27
28constexpr In4Addr addrToSubnet(In4Addr a, std::size_t pfx) noexcept
29{
William A. Kennington III3bd5add2023-06-28 11:56:15 -070030 return In4Addr{in_addr{a.word() & addr32Mask(pfx)}};
William A. Kennington III14dd4eb2023-01-12 10:51:12 -080031}
32
33constexpr 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
45constexpr 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
51constexpr bool subnetContains(auto, auto, std::size_t) noexcept
52{
53 return false;
54}
55
56template <typename T>
57constexpr bool subnetContains(T l, T r, std::size_t pfx) noexcept
58{
59 return addrToSubnet(l, pfx) == addrToSubnet(r, pfx);
60}
61
62constexpr 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
68constexpr std::size_t addrBits(auto a) noexcept
69{
70 return sizeof(a) << 3;
71}
72
73void invalidSubnetPfx(std::size_t pfx);
74
William A. Kennington IIId5957f52023-06-16 16:55:01 -070075template <typename Addr_, typename Pfx_>
William A. Kennington III14dd4eb2023-01-12 10:51:12 -080076class Subnet46
77{
William A. Kennington IIId5957f52023-06-16 16:55:01 -070078 public:
79 using Addr = Addr_;
80 using Pfx = Pfx_;
81
William A. Kennington III14dd4eb2023-01-12 10:51:12 -080082 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 IIIc870ac32023-06-17 17:13:45 -0700127template <typename T>
128constexpr T pfxToMask(std::uint8_t pfx);
129
130template <>
131constexpr In4Addr pfxToMask<In4Addr>(std::uint8_t pfx)
132{
133 return in_addr{detail::addr32Mask(pfx)};
134}
135
136constexpr std::uint8_t maskToPfx(In4Addr mask)
137{
William A. Kennington III3bd5add2023-06-28 11:56:15 -0700138 uint32_t x = ntoh(mask.word());
William A. Kennington IIIc870ac32023-06-17 17:13:45 -0700139 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 IIId5957f52023-06-16 16:55:01 -0700146using Subnet4 = detail::Subnet46<In4Addr, std::uint8_t>;
147using Subnet6 = detail::Subnet46<In6Addr, std::uint8_t>;
William A. Kennington III14dd4eb2023-01-12 10:51:12 -0800148
149class SubnetAny
150{
William A. Kennington IIId5957f52023-06-16 16:55:01 -0700151 public:
152 using Addr = InAnyAddr;
153 using Pfx = std::uint8_t;
154
William A. Kennington III14dd4eb2023-01-12 10:51:12 -0800155 private:
William A. Kennington IIId5957f52023-06-16 16:55:01 -0700156 Addr addr;
157 Pfx pfx;
William A. Kennington III14dd4eb2023-01-12 10:51:12 -0800158
159 public:
William A. Kennington IIId5957f52023-06-16 16:55:01 -0700160 constexpr SubnetAny(auto addr, Pfx pfx) : addr(addr), pfx(pfx)
William A. Kennington III14dd4eb2023-01-12 10:51:12 -0800161 {
162 if (detail::addrBits(addr) < pfx)
163 {
164 detail::invalidSubnetPfx(pfx);
165 }
166 }
William A. Kennington IIId5957f52023-06-16 16:55:01 -0700167 constexpr SubnetAny(InAnyAddr addr, Pfx pfx) : addr(addr), pfx(pfx)
William A. Kennington III14dd4eb2023-01-12 10:51:12 -0800168 {
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 IIId5957f52023-06-16 16:55:01 -0700227namespace detail
228{
229
230template <typename Subnet>
William A. Kennington III160e3822023-06-16 17:09:43 -0700231struct 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
246template <typename Subnet>
William A. Kennington IIId5957f52023-06-16 16:55:01 -0700247struct 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 IIIb03c9cb2023-06-17 16:36:21 -0700264template <typename Subnet>
265struct 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 IIId5957f52023-06-16 16:55:01 -0700273} // namespace detail
274
275template <typename Addr, typename Pfx>
William A. Kennington III160e3822023-06-16 17:09:43 -0700276struct FromStr<detail::Subnet46<Addr, Pfx>> :
277 detail::SubnetFromStr<detail::Subnet46<Addr, Pfx>>
278{};
279
280template <>
281struct FromStr<SubnetAny> : detail::SubnetFromStr<SubnetAny>
282{};
283
284template <typename Addr, typename Pfx>
William A. Kennington IIId5957f52023-06-16 16:55:01 -0700285struct ToStr<detail::Subnet46<Addr, Pfx>> :
286 detail::SubnetToStr<detail::Subnet46<Addr, Pfx>>
287{};
288
289template <>
290struct ToStr<SubnetAny> : detail::SubnetToStr<SubnetAny>
291{};
292
William A. Kennington III14dd4eb2023-01-12 10:51:12 -0800293} // namespace stdplus
William A. Kennington IIId5957f52023-06-16 16:55:01 -0700294
295template <typename Addr, typename Pfx, typename CharT>
296struct fmt::formatter<stdplus::detail::Subnet46<Addr, Pfx>, CharT> :
297 stdplus::Format<stdplus::ToStr<stdplus::detail::Subnet46<Addr, Pfx>>, CharT>
298{};
299
300template <typename CharT>
301struct fmt::formatter<stdplus::SubnetAny, CharT> :
302 stdplus::Format<stdplus::ToStr<stdplus::SubnetAny>, CharT>
303{};
William A. Kennington IIIb03c9cb2023-06-17 16:36:21 -0700304
305template <typename Addr, typename Pfx>
306struct std::hash<stdplus::detail::Subnet46<Addr, Pfx>> :
307 stdplus::detail::SubnetHash<stdplus::detail::Subnet46<Addr, Pfx>>
308{};
309
310template <>
311struct std::hash<stdplus::SubnetAny> :
312 stdplus::detail::SubnetHash<stdplus::SubnetAny>
313{};