TLA Line data 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 HIT 145359 : 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 15210 : 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 227 : ip_address(ipv6_address const& addr) noexcept
99 454 : : v6_(addr)
100 227 : , family_(corosio::family::v6)
101 : {
102 227 : }
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 209 : corosio::family family() const noexcept
134 : {
135 209 : 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 16116 : bool is_v4() const noexcept
143 : {
144 16116 : 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 69 : bool is_v6() const noexcept
152 : {
153 69 : 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 13 : bool is_loopback() const noexcept
162 : {
163 13 : 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 6 : bool is_unspecified() const noexcept
172 : {
173 6 : 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 4 : bool is_multicast() const noexcept
182 : {
183 4 : 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 3 : bool is_v4_mapped() const noexcept
195 : {
196 3 : 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 5455 : ipv4_address to_v4() const
214 : {
215 5455 : 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 89 : ipv6_address to_v6() const
232 : {
233 89 : if (is_v4())
234 2 : detail::throw_system_error(
235 2 : std::make_error_code(std::errc::address_family_not_supported),
236 : "address is not IPv6");
237 87 : 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 13 : std::string to_string() const
248 : {
249 13 : 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 127 : friend bool operator==(ip_address const& a1, ip_address const& a2) noexcept
278 : {
279 127 : if (a1.family_ != a2.family_)
280 8 : return false;
281 119 : 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 33 : operator<=>(ip_address const& a1, ip_address const& a2) noexcept
296 : {
297 33 : if (a1.family_ != a2.family_)
298 11 : return a1.is_v4() ? std::strong_ordering::less
299 11 : : std::strong_ordering::greater;
300 22 : 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 25 : inline ip_address::ip_address(std::string_view s)
333 : {
334 25 : auto [ec, addr] = make_ip_address(s);
335 25 : if (ec)
336 2 : detail::throw_system_error(ec, "invalid IP address");
337 23 : *this = addr;
338 23 : }
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 25 : operator()(boost::corosio::ip_address const& addr) const noexcept
351 : {
352 : // Family-guarded dispatch keeps the throwing conversions
353 : // unreachable
354 25 : return addr.is_v4()
355 25 : ? hash<boost::corosio::ipv4_address>()(addr.to_v4())
356 25 : : hash<boost::corosio::ipv6_address>()(addr.to_v6());
357 : }
358 : };
359 :
360 : } // namespace std
361 :
362 : #endif
|