include/boost/corosio/ip_address.hpp

100.0% Lines (52/52) 100.0% List of functions (17/18) 93.8% Branches (30/32)
ip_address.hpp
f(x) Functions (18)
Function Calls Lines Branches Blocks
<unknown function 65> :65 – – – – boost::corosio::ip_address::ip_address() :76 549564x 100.0% – 100.0% boost::corosio::ip_address::ip_address(boost::corosio::ipv4_address const&) :92 59094x 100.0% – 100.0% boost::corosio::ip_address::ip_address(boost::corosio::ipv6_address const&) :98 454x 100.0% – 100.0% boost::corosio::ip_address::family() const :133 195x 100.0% – 100.0% boost::corosio::ip_address::is_v4() const :142 30449x 100.0% – 100.0% boost::corosio::ip_address::is_v6() const :151 69x 100.0% – 100.0% boost::corosio::ip_address::is_loopback() const :161 14x 100.0% 100.0% 100.0% boost::corosio::ip_address::is_unspecified() const :171 6x 100.0% 100.0% 100.0% boost::corosio::ip_address::is_multicast() const :181 4x 100.0% 100.0% 100.0% boost::corosio::ip_address::is_v4_mapped() const :194 3x 100.0% 100.0% 100.0% boost::corosio::ip_address::to_v4() const :213 10812x 100.0% 100.0% 100.0% boost::corosio::ip_address::to_v6() const :231 85x 100.0% 100.0% 100.0% boost::corosio::ip_address::to_string() const :247 13x 100.0% 100.0% 100.0% boost::corosio::operator==(boost::corosio::ip_address const&, boost::corosio::ip_address const&) :277 128x 100.0% 100.0% 100.0% boost::corosio::operator<=>(boost::corosio::ip_address const&, boost::corosio::ip_address const&) :295 30x 100.0% 100.0% 100.0% boost::corosio::ip_address::ip_address(std::__1::basic_string_view<char, std::__1::char_traits<char>>) :332 50x 100.0% 100.0% 100.0% std::__1::hash<boost::corosio::ip_address>::operator()(boost::corosio::ip_address const&) const :350 17x 100.0% 66.7% 85.0%
Line Branch TLA Hits Source Code
1 //
2 // Copyright (c) 2026 Vinnie Falco ([email protected])
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 304354x 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 824346x 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 88641x 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 454x ip_address(ipv6_address const& addr) noexcept
99 227x : v6_(addr)
100 227x , family_(corosio::family::v6)
101 227x {
102 454x }
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 195x corosio::family family() const noexcept
134 {
135 195x 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 30449x bool is_v4() const noexcept
143 {
144 30449x 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 14x bool is_loopback() const noexcept
162 {
163
2/2
✓ Branch 0 taken 4 times.
✓ Branch 1 taken 10 times.
14x 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
2/2
✓ Branch 0 taken 4 times.
✓ Branch 1 taken 2 times.
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
2/2
✓ Branch 0 taken 2 times.
✓ Branch 1 taken 2 times.
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
2/2
✓ Branch 0 taken 1 time.
✓ Branch 1 taken 2 times.
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 10812x ipv4_address to_v4() const
214 {
215
2/2
✓ Branch 0 taken 10808 times.
✓ Branch 1 taken 4 times.
10812x 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 85x ipv6_address to_v6() const
232 {
233
2/2
✓ Branch 0 taken 83 times.
✓ Branch 1 taken 2 times.
85x 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 83x 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
2/2
✓ Branch 0 taken 9 times.
✓ Branch 1 taken 4 times.
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 128x friend bool operator==(ip_address const& a1, ip_address const& a2) noexcept
278 {
279
2/2
✓ Branch 0 taken 9 times.
✓ Branch 1 taken 119 times.
128x if (a1.family_ != a2.family_)
280 9x return false;
281
2/2
✓ Branch 0 taken 107 times.
✓ Branch 1 taken 12 times.
119x return a1.is_v4() ? a1.v4_ == a2.v4_ : a1.v6_ == a2.v6_;
282 128x }
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 30x operator<=>(ip_address const& a1, ip_address const& a2) noexcept
296 {
297
2/2
✓ Branch 0 taken 8 times.
✓ Branch 1 taken 22 times.
30x if (a1.family_ != a2.family_)
298
2/2
✓ Branch 0 taken 5 times.
✓ Branch 1 taken 3 times.
8x return a1.is_v4() ? std::strong_ordering::less
299 : std::strong_ordering::greater;
300
2/2
✓ Branch 0 taken 15 times.
✓ Branch 1 taken 7 times.
22x return a1.is_v4() ? a1.v4_ <=> a2.v4_ : a1.v6_ <=> a2.v6_;
301 30x }
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 50x inline ip_address::ip_address(std::string_view s)
333 25x {
334 25x auto [ec, addr] = make_ip_address(s);
335
2/2
✓ Branch 0 taken 23 times.
✓ Branch 1 taken 2 times.
25x if (ec)
336 2x detail::throw_system_error(ec, "invalid IP address");
337 23x *this = addr;
338 48x }
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 17x operator()(boost::corosio::ip_address const& addr) const noexcept
351 {
352 // Family-guarded dispatch keeps the throwing conversions
353 // unreachable
354
2/2
✓ Branch 0 taken 9 times.
✓ Branch 1 taken 8 times.
34x return addr.is_v4()
355
1/2
✓ Branch 0 taken 9 times.
✗ Branch 1 not taken.
9x ? hash<boost::corosio::ipv4_address>()(addr.to_v4())
356
1/2
✓ Branch 0 taken 8 times.
✗ Branch 1 not taken.
8x : hash<boost::corosio::ipv6_address>()(addr.to_v6());
357 }
358 };
359
360 } // namespace std
361
362 #endif
363