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