TLA Line data Source code
1 : //
2 : // Copyright (c) 2026 Vinnie Falco (vinnie.falco@gmail.com)
3 : // Copyright (c) 2026 Steve Gerbino
4 : //
5 : // Distributed under the Boost Software License, Version 1.0. (See accompanying
6 : // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
7 : //
8 : // Official repository: https://github.com/cppalliance/corosio
9 : //
10 :
11 : #ifndef BOOST_COROSIO_IP_ADDRESS_HPP
12 : #define BOOST_COROSIO_IP_ADDRESS_HPP
13 :
14 : #include <boost/corosio/detail/config.hpp>
15 : #include <boost/corosio/detail/except.hpp>
16 : #include <boost/corosio/family.hpp>
17 : #include <boost/corosio/ipv4_address.hpp>
18 : #include <boost/corosio/ipv6_address.hpp>
19 :
20 : #include <boost/capy/io_result.hpp>
21 :
22 : #include <compare>
23 : #include <iosfwd>
24 : #include <string>
25 : #include <string_view>
26 : #include <system_error>
27 :
28 : namespace boost::corosio {
29 :
30 : /** A version-independent IP address.
31 :
32 : This class holds either an IPv4 or an IPv6 address, letting
33 : code that works with both families carry one value instead of
34 : branching between @ref ipv4_address and @ref ipv6_address.
35 : Family-generic queries such as @ref is_loopback dispatch to
36 : the held address, and @ref to_v4 / @ref to_v6 recover the
37 : family-specific form.
38 :
39 : A v4-mapped IPv6 address (`::ffff:a.b.c.d`) is an IPv6-family
40 : value: it does not compare equal to the IPv4 address it maps.
41 : To compare across the mapping, normalize both sides with
42 : @ref to_v4 first.
43 :
44 : @par Thread Safety
45 : Distinct objects: Safe.@n
46 : Shared objects: Safe.
47 :
48 : @par Example
49 : @code
50 : ip_address addr("2001:db8::1");
51 : if (addr.is_loopback())
52 : {
53 : // family-generic query, no branching
54 : }
55 : @endcode
56 :
57 : @see
58 : @ref ipv4_address,
59 : @ref ipv6_address,
60 : @ref make_ip_address.
61 : */
62 : class BOOST_COROSIO_DECL ip_address
63 : {
64 : ipv4_address v4_;
65 : ipv6_address v6_;
66 : corosio::family family_ = corosio::family::v4;
67 :
68 : public:
69 : /** The number of characters in the longest possible address string.
70 : */
71 : static constexpr std::size_t max_str_len = ipv6_address::max_str_len;
72 :
73 : /** Default constructor.
74 :
75 : Constructs the IPv4 unspecified address (0.0.0.0).
76 : */
77 HIT 144991 : ip_address() = default;
78 :
79 : /** Copy constructor.
80 : */
81 : ip_address(ip_address const&) = default;
82 :
83 : /** Copy assignment.
84 :
85 : @return A reference to this object.
86 : */
87 : ip_address& operator=(ip_address const&) = default;
88 :
89 : /** Construct from an IPv4 address.
90 :
91 : @param addr The address to hold.
92 : */
93 15189 : ip_address(ipv4_address const& addr) noexcept : v4_(addr) {}
94 :
95 : /** Construct from an IPv6 address.
96 :
97 : @param addr The address to hold.
98 : */
99 227 : ip_address(ipv6_address const& addr) noexcept
100 454 : : v6_(addr)
101 227 : , family_(corosio::family::v6)
102 : {
103 227 : }
104 :
105 : /** Construct from a string.
106 :
107 : This function constructs an address from the string `s`,
108 : which must contain a valid IPv4 or IPv6 address string
109 : or else an exception is thrown.
110 :
111 : @par Exception Safety
112 : Strong guarantee.
113 :
114 : @throws std::system_error `errc::invalid_argument` if the input
115 : failed to parse correctly.
116 :
117 : @note For a non-throwing parse function,
118 : use @ref make_ip_address.
119 :
120 : @param s The string to parse.
121 :
122 : @see
123 : @ref make_ip_address.
124 : */
125 : explicit ip_address(std::string_view s);
126 :
127 : /** Return the address family.
128 :
129 : The portable spelling of the family; @ref is_v4 and
130 : @ref is_v6 are sugar over it.
131 :
132 : @return The family of the held address.
133 : */
134 209 : corosio::family family() const noexcept
135 : {
136 209 : return family_;
137 : }
138 :
139 : /** Check if the held address is IPv4.
140 :
141 : @return `true` if the address is IPv4, `false` if IPv6.
142 : */
143 16097 : bool is_v4() const noexcept
144 : {
145 16097 : return family_ == corosio::family::v4;
146 : }
147 :
148 : /** Check if the held address is IPv6.
149 :
150 : @return `true` if the address is IPv6, `false` if IPv4.
151 : */
152 69 : bool is_v6() const noexcept
153 : {
154 69 : return family_ == corosio::family::v6;
155 : }
156 :
157 : /** Check if the address is a loopback address.
158 :
159 : @return `true` if the held address is a loopback
160 : address of its family.
161 : */
162 13 : bool is_loopback() const noexcept
163 : {
164 13 : return is_v4() ? v4_.is_loopback() : v6_.is_loopback();
165 : }
166 :
167 : /** Check if the address is unspecified.
168 :
169 : @return `true` if the held address is the unspecified
170 : address of its family.
171 : */
172 6 : bool is_unspecified() const noexcept
173 : {
174 6 : return is_v4() ? v4_.is_unspecified() : v6_.is_unspecified();
175 : }
176 :
177 : /** Check if the address is a multicast address.
178 :
179 : @return `true` if the held address is a multicast
180 : address of its family.
181 : */
182 4 : bool is_multicast() const noexcept
183 : {
184 4 : return is_v4() ? v4_.is_multicast() : v6_.is_multicast();
185 : }
186 :
187 : /** Check if the address is a v4-mapped IPv6 address.
188 :
189 : @return `true` if the address is IPv6 and is an
190 : IPv4-Mapped IPv6 Address (`::ffff:a.b.c.d`).
191 :
192 : @see
193 : @ref to_v4.
194 : */
195 3 : bool is_v4_mapped() const noexcept
196 : {
197 3 : return is_v6() && v6_.is_v4_mapped();
198 : }
199 :
200 : /** Convert to an IPv4 address.
201 :
202 : Returns the held IPv4 address, or the IPv4 address that a
203 : v4-mapped IPv6 address maps. This makes normalize-then-compare
204 : a single call when matching addresses across the mapping.
205 :
206 : @throws std::system_error `errc::address_family_not_supported`
207 : if the address is IPv6 and not v4-mapped.
208 :
209 : @return The IPv4 form of the address.
210 :
211 : @see
212 : @ref is_v4, @ref is_v4_mapped.
213 : */
214 5450 : ipv4_address to_v4() const
215 : {
216 5450 : return is_v4() ? v4_ : v6_.to_v4();
217 : }
218 :
219 : /** Convert to an IPv6 address.
220 :
221 : To map an IPv4 address into IPv6, use the
222 : `ipv6_address(ipv4_address const&)` constructor instead.
223 :
224 : @throws std::system_error `errc::address_family_not_supported`
225 : if the address is IPv4.
226 :
227 : @return The held IPv6 address.
228 :
229 : @see
230 : @ref is_v6.
231 : */
232 89 : ipv6_address to_v6() const
233 : {
234 89 : if (is_v4())
235 2 : detail::throw_system_error(
236 2 : std::make_error_code(std::errc::address_family_not_supported),
237 : "address is not IPv6");
238 87 : return v6_;
239 : }
240 :
241 : /** Return the address as a string.
242 :
243 : IPv4 addresses format in dotted decimal, IPv6 addresses
244 : in standard notation without surrounding brackets.
245 :
246 : @return The address as a string.
247 : */
248 13 : std::string to_string() const
249 : {
250 13 : return is_v4() ? v4_.to_string() : v6_.to_string();
251 : }
252 :
253 : /** Write a string representing the address to a buffer.
254 :
255 : The resulting buffer is not null-terminated.
256 :
257 : @throws std::length_error `dest_size < ip_address::max_str_len`
258 :
259 : @param dest The buffer in which to write,
260 : which must have at least `dest_size` space.
261 :
262 : @param dest_size The size of the output buffer.
263 :
264 : @return The formatted string view.
265 : */
266 : std::string_view to_buffer(char* dest, std::size_t dest_size) const;
267 :
268 : /** Return true if two addresses are equal.
269 :
270 : Addresses are equal if they have the same family and the
271 : same value. A v4-mapped IPv6 address is not equal to the
272 : IPv4 address it maps; normalize with @ref to_v4 to compare
273 : across the mapping.
274 :
275 : @return `true` if the addresses are equal.
276 : */
277 127 : friend bool operator==(ip_address const& a1, ip_address const& a2) noexcept
278 : {
279 127 : if (a1.family_ != a2.family_)
280 8 : return false;
281 119 : return a1.is_v4() ? a1.v4_ == a2.v4_ : a1.v6_ == a2.v6_;
282 : }
283 :
284 : /** Order two addresses.
285 :
286 : Establishes a strict total ordering consistent with
287 : @ref operator==: addresses are ordered first by family
288 : (IPv4 before IPv6), then by value. This makes `ip_address`
289 : usable as a key in ordered containers such as `std::map`
290 : and `std::set`.
291 :
292 : @return The relative order of `a1` and `a2`.
293 : */
294 : friend std::strong_ordering
295 33 : operator<=>(ip_address const& a1, ip_address const& a2) noexcept
296 : {
297 33 : if (a1.family_ != a2.family_)
298 11 : return a1.is_v4() ? std::strong_ordering::less
299 11 : : std::strong_ordering::greater;
300 22 : return a1.is_v4() ? a1.v4_ <=> a2.v4_ : a1.v6_ <=> a2.v6_;
301 : }
302 :
303 : /** Format the address to an output stream.
304 :
305 : @param os The output stream.
306 : @param addr The address to format.
307 : @return The output stream.
308 : */
309 : friend BOOST_COROSIO_DECL std::ostream&
310 : operator<<(std::ostream& os, ip_address const& addr);
311 : };
312 :
313 : /** Create an IP address from a string.
314 :
315 : This function parses `s` as an IPv4 address in dotted decimal
316 : form, or an IPv6 address in hexadecimal notation, optionally
317 : qualified by a `%zone` suffix (a decimal interface index, or an
318 : interface name where the platform names interfaces). The string
319 : must contain the address alone: port suffixes, surrounding
320 : brackets, and host names are not accepted.
321 :
322 : @par Exception Safety
323 : Throws nothing.
324 :
325 : @param s The string to parse.
326 : @return The error code, empty on success, and the parsed
327 : address — default-constructed on failure.
328 : */
329 : [[nodiscard]] BOOST_COROSIO_DECL capy::io_result<ip_address>
330 : make_ip_address(std::string_view s) noexcept;
331 :
332 25 : inline ip_address::ip_address(std::string_view s)
333 : {
334 25 : auto [ec, addr] = make_ip_address(s);
335 25 : if (ec)
336 2 : detail::throw_system_error(ec, "invalid IP address");
337 23 : *this = addr;
338 23 : }
339 :
340 : } // namespace boost::corosio
341 :
342 : namespace std {
343 :
344 : /// Hash support for `boost::corosio::ip_address`.
345 : template<>
346 : struct hash<boost::corosio::ip_address>
347 : {
348 : /// Return the hash of `addr`.
349 : std::size_t
350 25 : operator()(boost::corosio::ip_address const& addr) const noexcept
351 : {
352 : // Family-guarded dispatch keeps the throwing conversions
353 : // unreachable
354 25 : return addr.is_v4()
355 25 : ? hash<boost::corosio::ipv4_address>()(addr.to_v4())
356 25 : : hash<boost::corosio::ipv6_address>()(addr.to_v6());
357 : }
358 : };
359 :
360 : } // namespace std
361 :
362 : #endif
|