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 // Copyright (c) 2026 Michael Vandeberg
5 //
6 // Distributed under the Boost Software License, Version 1.0. (See accompanying
7 // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
8 //
9 // Official repository: https://github.com/cppalliance/corosio
10 //
11
12 #ifndef BOOST_COROSIO_IP_ADDRESS_HPP
13 #define BOOST_COROSIO_IP_ADDRESS_HPP
14
15 #include <boost/corosio/detail/config.hpp>
16 #include <boost/corosio/detail/except.hpp>
17 #include <boost/corosio/family.hpp>
18 #include <boost/corosio/ipv4_address.hpp>
19 #include <boost/corosio/ipv6_address.hpp>
20
21 #include <boost/capy/results.hpp>
22
23 #include <compare>
24 #include <iosfwd>
25 #include <string>
26 #include <string_view>
27 #include <system_error>
28
29 namespace boost::corosio {
30
31 /** A version-independent IP address.
32
33 This class holds either an IPv4 or an IPv6 address. Code that works with
34 both families carries one value instead of branching between @ref
35 ipv4_address and @ref ipv6_address. Family-generic queries such as
36 @ref is_loopback dispatch to the held address, and @ref to_v4 /
37 @ref to_v6 recover the 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 258834x 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 34272x 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 637x corosio::family family() const noexcept
135 {
136 637x 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 28936x bool is_v4() const noexcept
144 {
145 28936x 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 10261x ipv4_address to_v4() const
215 {
216 10261x 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 ip_address::to_v4
273 to compare
274 across the mapping.
275
276 @return `true` if the addresses are equal.
277 */
278 143x friend bool operator==(ip_address const& a1, ip_address const& a2) noexcept
279 {
280 143x if (a1.family_ != a2.family_)
281 8x return false;
282 135x return a1.is_v4() ? a1.v4_ == a2.v4_ : a1.v6_ == a2.v6_;
283 }
284
285 /** Order two addresses.
286
287 Establishes a strict total ordering consistent with
288 @ref operator==: addresses are ordered first by family
289 (IPv4 before IPv6), then by value. This makes `ip_address`
290 usable as a key in ordered containers such as `std::map`
291 and `std::set`.
292
293 @return The relative order of `a1` and `a2`.
294 */
295 friend std::strong_ordering
296 33x operator<=>(ip_address const& a1, ip_address const& a2) noexcept
297 {
298 33x if (a1.family_ != a2.family_)
299 11x return a1.is_v4() ? std::strong_ordering::less
300 11x : std::strong_ordering::greater;
301 22x return a1.is_v4() ? a1.v4_ <=> a2.v4_ : a1.v6_ <=> a2.v6_;
302 }
303
304 /** Format the address to an output stream.
305
306 @param os The output stream.
307 @param addr The address to format.
308 @return The output stream.
309 */
310 friend BOOST_COROSIO_DECL std::ostream&
311 operator<<(std::ostream& os, ip_address const& addr);
312 };
313
314 /** Create an IP address from a string.
315
316 This function parses `s` as an IPv4 address in dotted decimal form, or
317 an IPv6 address in hexadecimal notation. An IPv6 address may carry a
318 `%zone` suffix: a decimal interface index, or an interface name where
319 the platform names interfaces. The string must contain the address
320 alone: port suffixes, surrounding brackets, and host names are not
321 accepted.
322
323 @par Exception Safety
324 Throws nothing.
325
326 @param s The string to parse.
327 @return The error code, empty on success, and the parsed
328 address — default-constructed on failure.
329 */
330 [[nodiscard]] BOOST_COROSIO_DECL capy::results<ip_address>
331 make_ip_address(std::string_view s) noexcept;
332
333 25x inline ip_address::ip_address(std::string_view s)
334 {
335 25x auto [ec, addr] = make_ip_address(s);
336 25x if (ec)
337 2x detail::throw_system_error(ec, "invalid IP address");
338 23x *this = addr;
339 23x }
340
341 } // namespace boost::corosio
342
343 namespace std {
344
345 /// Hash support for `boost::corosio::ip_address`.
346 template<>
347 struct hash<boost::corosio::ip_address>
348 {
349 /// Return the hash of `addr`.
350 std::size_t
351 25x operator()(boost::corosio::ip_address const& addr) const noexcept
352 {
353 // Family-guarded dispatch keeps the throwing conversions
354 // unreachable
355 25x return addr.is_v4()
356 25x ? hash<boost::corosio::ipv4_address>()(addr.to_v4())
357 25x : hash<boost::corosio::ipv6_address>()(addr.to_v6());
358 }
359 };
360
361 } // namespace std
362
363 #endif
364