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 : 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 HIT 269341 : 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 35267 : 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 227 : ip_address(ipv6_address const& addr) noexcept
102 454 : : v6_(addr)
103 227 : , family_(corosio::family::v6)
104 : {
105 227 : }
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 647 : corosio::family family() const noexcept
115 : {
116 647 : 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 30652 : bool is_v4() const noexcept
124 : {
125 30652 : 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 69 : bool is_v6() const noexcept
133 : {
134 69 : 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 13 : bool is_loopback() const noexcept
143 : {
144 13 : 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 6 : bool is_unspecified() const noexcept
153 : {
154 6 : 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 4 : bool is_multicast() const noexcept
163 : {
164 4 : 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 3 : bool is_v4_mapped() const noexcept
176 : {
177 3 : 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 10842 : ipv4_address to_v4() const
195 : {
196 10842 : 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 89 : ipv6_address to_v6() const
213 : {
214 89 : if (is_v4())
215 2 : detail::throw_system_error(
216 2 : std::make_error_code(std::errc::address_family_not_supported),
217 : "address is not IPv6");
218 87 : 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 13 : std::string to_string() const
229 : {
230 13 : 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 160 : friend bool operator==(ip_address const& a1, ip_address const& a2) noexcept
259 : {
260 160 : if (a1.family_ != a2.family_)
261 8 : return false;
262 152 : 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 33 : operator<=>(ip_address const& a1, ip_address const& a2) noexcept
277 : {
278 33 : if (a1.family_ != a2.family_)
279 11 : return a1.is_v4() ? std::strong_ordering::less
280 11 : : std::strong_ordering::greater;
281 22 : 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 25 : operator()(boost::corosio::ip_address const& addr) const noexcept
324 : {
325 : // Family-guarded dispatch keeps the throwing conversions
326 : // unreachable
327 25 : return addr.is_v4()
328 25 : ? hash<boost::corosio::ipv4_address>()(addr.to_v4())
329 25 : : hash<boost::corosio::ipv6_address>()(addr.to_v6());
330 : }
331 : };
332 :
333 : } // namespace std
334 :
335 : #endif
|