include/boost/corosio/ip_address.hpp

100.0% Lines (48 / 48) 100.0% Functions (17 / 17)
ip_address.hpp
f(x) Functions (17)
Line TLA Hits 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 140341x 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 14724x 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 227x ip_address(ipv6_address const& addr) noexcept
100 454x : v6_(addr)
101 227x , family_(corosio::family::v6)
102 {
103 227x }
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 209x corosio::family family() const noexcept
135 {
136 209x 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 15632x bool is_v4() const noexcept
144 {
145 15632x 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 69x bool is_v6() const noexcept
153 {
154 69x 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 13x bool is_loopback() const noexcept
163 {
164 13x 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 6x bool is_unspecified() const noexcept
173 {
174 6x 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 4x bool is_multicast() const noexcept
183 {
184 4x 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 3x bool is_v4_mapped() const noexcept
196 {
197 3x 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 5295x ipv4_address to_v4() const
215 {
216 5295x 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 89x ipv6_address to_v6() const
233 {
234 89x if (is_v4())
235 2x detail::throw_system_error(
236 2x std::make_error_code(std::errc::address_family_not_supported),
237 "address is not IPv6");
238 87x 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 13x std::string to_string() const
249 {
250 13x 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 127x friend bool operator==(ip_address const& a1, ip_address const& a2) noexcept
278 {
279 127x if (a1.family_ != a2.family_)
280 8x return false;
281 119x 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 33x operator<=>(ip_address const& a1, ip_address const& a2) noexcept
296 {
297 33x if (a1.family_ != a2.family_)
298 11x return a1.is_v4() ? std::strong_ordering::less
299 11x : std::strong_ordering::greater;
300 22x 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 25x inline ip_address::ip_address(std::string_view s)
333 {
334 25x auto [ec, addr] = make_ip_address(s);
335 25x if (ec)
336 2x detail::throw_system_error(ec, "invalid IP address");
337 23x *this = addr;
338 23x }
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 25x operator()(boost::corosio::ip_address const& addr) const noexcept
351 {
352 // Family-guarded dispatch keeps the throwing conversions
353 // unreachable
354 25x return addr.is_v4()
355 25x ? hash<boost::corosio::ipv4_address>()(addr.to_v4())
356 25x : hash<boost::corosio::ipv6_address>()(addr.to_v6());
357 }
358 };
359
360 } // namespace std
361
362 #endif
363