include/boost/corosio/ip_address.hpp

100.0% Lines (42 / 42) 100.0% Functions (16 / 16)
ip_address.hpp
f(x) Functions (16)
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 auto [ec, addr] = make_ip_address("2001:db8::1");
51 if (ec)
52 return ec; // not a valid address string
53 if (addr.is_loopback())
54 {
55 // family-generic query, no branching
56 }
57 @endcode
58
59 @see
60 @ref ipv4_address,
61 @ref ipv6_address,
62 @ref make_ip_address
63 */
64 class BOOST_COROSIO_DECL ip_address
65 {
66 ipv4_address v4_;
67 ipv6_address v6_;
68 corosio::family family_ = corosio::family::v4;
69
70 public:
71 /** The number of characters in the longest possible address string.
72 */
73 static constexpr std::size_t max_str_len = ipv6_address::max_str_len;
74
75 /** Default constructor.
76
77 Constructs the IPv4 unspecified address (0.0.0.0).
78 */
79 269341x ip_address() = default;
80
81 /** Copy constructor.
82 */
83 ip_address(ip_address const&) = default;
84
85 /** Copy assignment.
86
87 @return A reference to this object.
88 */
89 ip_address& operator=(ip_address const&) = default;
90
91 /** Construct from an IPv4 address.
92
93 @param addr The address to hold.
94 */
95 35267x ip_address(ipv4_address const& addr) noexcept : v4_(addr) {}
96
97 /** Construct from an IPv6 address.
98
99 @param addr The address to hold.
100 */
101 227x ip_address(ipv6_address const& addr) noexcept
102 454x : v6_(addr)
103 227x , family_(corosio::family::v6)
104 {
105 227x }
106
107 /** Return the address family.
108
109 The portable spelling of the family; @ref is_v4 and
110 @ref is_v6 are sugar over it.
111
112 @return The family of the held address.
113 */
114 647x corosio::family family() const noexcept
115 {
116 647x return family_;
117 }
118
119 /** Check if the held address is IPv4.
120
121 @return `true` if the address is IPv4, `false` if IPv6.
122 */
123 30652x bool is_v4() const noexcept
124 {
125 30652x return family_ == corosio::family::v4;
126 }
127
128 /** Check if the held address is IPv6.
129
130 @return `true` if the address is IPv6, `false` if IPv4.
131 */
132 69x bool is_v6() const noexcept
133 {
134 69x return family_ == corosio::family::v6;
135 }
136
137 /** Check if the address is a loopback address.
138
139 @return `true` if the held address is a loopback
140 address of its family.
141 */
142 13x bool is_loopback() const noexcept
143 {
144 13x return is_v4() ? v4_.is_loopback() : v6_.is_loopback();
145 }
146
147 /** Check if the address is unspecified.
148
149 @return `true` if the held address is the unspecified
150 address of its family.
151 */
152 6x bool is_unspecified() const noexcept
153 {
154 6x return is_v4() ? v4_.is_unspecified() : v6_.is_unspecified();
155 }
156
157 /** Check if the address is a multicast address.
158
159 @return `true` if the held address is a multicast
160 address of its family.
161 */
162 4x bool is_multicast() const noexcept
163 {
164 4x return is_v4() ? v4_.is_multicast() : v6_.is_multicast();
165 }
166
167 /** Check if the address is a v4-mapped IPv6 address.
168
169 @return `true` if the address is IPv6 and is an
170 IPv4-Mapped IPv6 Address (`::ffff:a.b.c.d`).
171
172 @see
173 @ref to_v4
174 */
175 3x bool is_v4_mapped() const noexcept
176 {
177 3x return is_v6() && v6_.is_v4_mapped();
178 }
179
180 /** Convert to an IPv4 address.
181
182 Returns the held IPv4 address, or the IPv4 address that a
183 v4-mapped IPv6 address maps. This makes normalize-then-compare
184 a single call when matching addresses across the mapping.
185
186 @throws std::system_error `errc::address_family_not_supported`
187 if the address is IPv6 and not v4-mapped.
188
189 @return The IPv4 form of the address.
190
191 @see
192 @ref is_v4, @ref is_v4_mapped
193 */
194 10842x ipv4_address to_v4() const
195 {
196 10842x return is_v4() ? v4_ : v6_.to_v4();
197 }
198
199 /** Convert to an IPv6 address.
200
201 To map an IPv4 address into IPv6, use the
202 `ipv6_address(ipv4_address const&)` constructor instead.
203
204 @throws std::system_error `errc::address_family_not_supported`
205 if the address is IPv4.
206
207 @return The held IPv6 address.
208
209 @see
210 @ref is_v6
211 */
212 89x ipv6_address to_v6() const
213 {
214 89x if (is_v4())
215 2x detail::throw_system_error(
216 2x std::make_error_code(std::errc::address_family_not_supported),
217 "address is not IPv6");
218 87x return v6_;
219 }
220
221 /** Return the address as a string.
222
223 IPv4 addresses format in dotted decimal, IPv6 addresses
224 in standard notation without surrounding brackets.
225
226 @return The address as a string.
227 */
228 13x std::string to_string() const
229 {
230 13x return is_v4() ? v4_.to_string() : v6_.to_string();
231 }
232
233 /** Write a string representing the address to a buffer.
234
235 The resulting buffer is not null-terminated.
236
237 @throws std::length_error `dest_size < ip_address::max_str_len`
238
239 @param dest The buffer in which to write,
240 which must have at least `dest_size` space.
241
242 @param dest_size The size of the output buffer.
243
244 @return The formatted string view.
245 */
246 std::string_view to_buffer(char* dest, std::size_t dest_size) const;
247
248 /** Return true if two addresses are equal.
249
250 Addresses are equal if they have the same family and the
251 same value. A v4-mapped IPv6 address is not equal to the
252 IPv4 address it maps; normalize with @ref ip_address::to_v4
253 to compare
254 across the mapping.
255
256 @return `true` if the addresses are equal.
257 */
258 160x friend bool operator==(ip_address const& a1, ip_address const& a2) noexcept
259 {
260 160x if (a1.family_ != a2.family_)
261 8x return false;
262 152x return a1.is_v4() ? a1.v4_ == a2.v4_ : a1.v6_ == a2.v6_;
263 }
264
265 /** Order two addresses.
266
267 Establishes a strict total ordering consistent with
268 @ref operator==: addresses are ordered first by family
269 (IPv4 before IPv6), then by value. This makes `ip_address`
270 usable as a key in ordered containers such as `std::map`
271 and `std::set`.
272
273 @return The relative order of `a1` and `a2`.
274 */
275 friend std::strong_ordering
276 33x operator<=>(ip_address const& a1, ip_address const& a2) noexcept
277 {
278 33x if (a1.family_ != a2.family_)
279 11x return a1.is_v4() ? std::strong_ordering::less
280 11x : std::strong_ordering::greater;
281 22x return a1.is_v4() ? a1.v4_ <=> a2.v4_ : a1.v6_ <=> a2.v6_;
282 }
283
284 /** Format the address to an output stream.
285
286 @param os The output stream.
287 @param addr The address to format.
288 @return The output stream.
289 */
290 friend BOOST_COROSIO_DECL std::ostream&
291 operator<<(std::ostream& os, ip_address const& addr);
292 };
293
294 /** Create an IP address from a string.
295
296 This function parses `s` as an IPv4 address in dotted decimal form, or
297 an IPv6 address in hexadecimal notation. An IPv6 address may carry a
298 `%zone` suffix: a decimal interface index, or an interface name where
299 the platform names interfaces. The string must contain the address
300 alone: port suffixes, surrounding brackets, and host names are not
301 accepted.
302
303 @par Exception Safety
304 Throws nothing.
305
306 @param s The string to parse.
307 @return The error code, empty on success, and the parsed
308 address — default-constructed on failure.
309 */
310 [[nodiscard]] BOOST_COROSIO_DECL capy::results<ip_address>
311 make_ip_address(std::string_view s) noexcept;
312
313 } // namespace boost::corosio
314
315 namespace std {
316
317 /// Hash support for `boost::corosio::ip_address`.
318 template<>
319 struct hash<boost::corosio::ip_address>
320 {
321 /// Return the hash of `addr`.
322 std::size_t
323 25x operator()(boost::corosio::ip_address const& addr) const noexcept
324 {
325 // Family-guarded dispatch keeps the throwing conversions
326 // unreachable
327 25x return addr.is_v4()
328 25x ? hash<boost::corosio::ipv4_address>()(addr.to_v4())
329 25x : hash<boost::corosio::ipv6_address>()(addr.to_v6());
330 }
331 };
332
333 } // namespace std
334
335 #endif
336