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. Code that works with
33 both families carries one value instead of branching between @ref
34 ipv4_address and @ref ipv6_address. Family-generic queries such as @ref
35 is_loopback dispatch to the held address, and @ref to_v4 / @ref to_v6
36 recover the family-specific form.
37
38 A v4-mapped IPv6 address (`::ffff:a.b.c.d`) is an IPv6-family
39 value: it does not compare equal to the IPv4 address it maps.
40 To compare across the mapping, normalize both sides with
41 @ref to_v4 first.
42
43 @par Thread Safety
44 Distinct objects: Safe.@n
45 Shared objects: Safe.
46
47 @par Example
48 @code
49 ip_address addr("2001:db8::1");
50 if (addr.is_loopback())
51 {
52 // family-generic query, no branching
53 }
54 @endcode
55
56 @see
57 @ref ipv4_address,
58 @ref ipv6_address,
59 @ref make_ip_address.
60 */
61 class BOOST_COROSIO_DECL ip_address
62 {
63 ipv4_address v4_;
64 ipv6_address v6_;
65 corosio::family family_ = corosio::family::v4;
66
67 public:
68 /** The number of characters in the longest possible address string.
69 */
70 static constexpr std::size_t max_str_len = ipv6_address::max_str_len;
71
72 /** Default constructor.
73
74 Constructs the IPv4 unspecified address (0.0.0.0).
75 */
76 145359x ip_address() = default;
77
78 /** Copy constructor.
79 */
80 ip_address(ip_address const&) = default;
81
82 /** Copy assignment.
83
84 @return A reference to this object.
85 */
86 ip_address& operator=(ip_address const&) = default;
87
88 /** Construct from an IPv4 address.
89
90 @param addr The address to hold.
91 */
92 15210x ip_address(ipv4_address const& addr) noexcept : v4_(addr) {}
93
94 /** Construct from an IPv6 address.
95
96 @param addr The address to hold.
97 */
98 227x ip_address(ipv6_address const& addr) noexcept
99 454x : v6_(addr)
100 227x , family_(corosio::family::v6)
101 {
102 227x }
103
104 /** Construct from a string.
105
106 This function constructs an address from the string `s`,
107 which must contain a valid IPv4 or IPv6 address string
108 or else an exception is thrown.
109
110 @par Exception Safety
111 Strong guarantee.
112
113 @throws std::system_error `errc::invalid_argument` if the input
114 failed to parse correctly.
115
116 @note For a non-throwing parse function,
117 use @ref make_ip_address.
118
119 @param s The string to parse.
120
121 @see
122 @ref make_ip_address.
123 */
124 explicit ip_address(std::string_view s);
125
126 /** Return the address family.
127
128 The portable spelling of the family; @ref is_v4 and
129 @ref is_v6 are sugar over it.
130
131 @return The family of the held address.
132 */
133 209x corosio::family family() const noexcept
134 {
135 209x return family_;
136 }
137
138 /** Check if the held address is IPv4.
139
140 @return `true` if the address is IPv4, `false` if IPv6.
141 */
142 16116x bool is_v4() const noexcept
143 {
144 16116x return family_ == corosio::family::v4;
145 }
146
147 /** Check if the held address is IPv6.
148
149 @return `true` if the address is IPv6, `false` if IPv4.
150 */
151 69x bool is_v6() const noexcept
152 {
153 69x return family_ == corosio::family::v6;
154 }
155
156 /** Check if the address is a loopback address.
157
158 @return `true` if the held address is a loopback
159 address of its family.
160 */
161 13x bool is_loopback() const noexcept
162 {
163 13x return is_v4() ? v4_.is_loopback() : v6_.is_loopback();
164 }
165
166 /** Check if the address is unspecified.
167
168 @return `true` if the held address is the unspecified
169 address of its family.
170 */
171 6x bool is_unspecified() const noexcept
172 {
173 6x return is_v4() ? v4_.is_unspecified() : v6_.is_unspecified();
174 }
175
176 /** Check if the address is a multicast address.
177
178 @return `true` if the held address is a multicast
179 address of its family.
180 */
181 4x bool is_multicast() const noexcept
182 {
183 4x return is_v4() ? v4_.is_multicast() : v6_.is_multicast();
184 }
185
186 /** Check if the address is a v4-mapped IPv6 address.
187
188 @return `true` if the address is IPv6 and is an
189 IPv4-Mapped IPv6 Address (`::ffff:a.b.c.d`).
190
191 @see
192 @ref to_v4.
193 */
194 3x bool is_v4_mapped() const noexcept
195 {
196 3x return is_v6() && v6_.is_v4_mapped();
197 }
198
199 /** Convert to an IPv4 address.
200
201 Returns the held IPv4 address, or the IPv4 address that a
202 v4-mapped IPv6 address maps. This makes normalize-then-compare
203 a single call when matching addresses across the mapping.
204
205 @throws std::system_error `errc::address_family_not_supported`
206 if the address is IPv6 and not v4-mapped.
207
208 @return The IPv4 form of the address.
209
210 @see
211 @ref is_v4, @ref is_v4_mapped.
212 */
213 5455x ipv4_address to_v4() const
214 {
215 5455x return is_v4() ? v4_ : v6_.to_v4();
216 }
217
218 /** Convert to an IPv6 address.
219
220 To map an IPv4 address into IPv6, use the
221 `ipv6_address(ipv4_address const&)` constructor instead.
222
223 @throws std::system_error `errc::address_family_not_supported`
224 if the address is IPv4.
225
226 @return The held IPv6 address.
227
228 @see
229 @ref is_v6.
230 */
231 89x ipv6_address to_v6() const
232 {
233 89x if (is_v4())
234 2x detail::throw_system_error(
235 2x std::make_error_code(std::errc::address_family_not_supported),
236 "address is not IPv6");
237 87x return v6_;
238 }
239
240 /** Return the address as a string.
241
242 IPv4 addresses format in dotted decimal, IPv6 addresses
243 in standard notation without surrounding brackets.
244
245 @return The address as a string.
246 */
247 13x std::string to_string() const
248 {
249 13x return is_v4() ? v4_.to_string() : v6_.to_string();
250 }
251
252 /** Write a string representing the address to a buffer.
253
254 The resulting buffer is not null-terminated.
255
256 @throws std::length_error `dest_size < ip_address::max_str_len`
257
258 @param dest The buffer in which to write,
259 which must have at least `dest_size` space.
260
261 @param dest_size The size of the output buffer.
262
263 @return The formatted string view.
264 */
265 std::string_view to_buffer(char* dest, std::size_t dest_size) const;
266
267 /** Return true if two addresses are equal.
268
269 Addresses are equal if they have the same family and the
270 same value. A v4-mapped IPv6 address is not equal to the
271 IPv4 address it maps; normalize with @ref ip_address::to_v4
272 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 form, or
316 an IPv6 address in hexadecimal notation. An IPv6 address may carry a
317 `%zone` suffix: a decimal interface index, or an interface name where
318 the platform names interfaces. The string must contain the address
319 alone: port suffixes, surrounding brackets, and host names are not
320 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