TLA Line data Source code
1 : //
2 : // Copyright (c) 2026 Vinnie Falco (vinnie.falco@gmail.com)
3 : //
4 : // Distributed under the Boost Software License, Version 1.0. (See accompanying
5 : // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
6 : //
7 : // Official repository: https://github.com/cppalliance/corosio
8 : //
9 :
10 : #ifndef BOOST_COROSIO_ENDPOINT_HPP
11 : #define BOOST_COROSIO_ENDPOINT_HPP
12 :
13 : #include <boost/corosio/detail/config.hpp>
14 : #include <boost/corosio/detail/except.hpp>
15 : #include <boost/corosio/ipv4_address.hpp>
16 : #include <boost/corosio/ipv6_address.hpp>
17 :
18 : #include <boost/capy/io_result.hpp>
19 :
20 : #include <compare>
21 : #include <cstdint>
22 : #include <string_view>
23 : #include <system_error>
24 :
25 : namespace boost::corosio {
26 :
27 : /** An IP endpoint (address + port) supporting both IPv4 and IPv6.
28 :
29 : This class represents an endpoint for IP communication,
30 : consisting of either an IPv4 or IPv6 address and a port number.
31 : Endpoints are used to specify connection targets and bind addresses.
32 :
33 : The endpoint holds both address types as separate members (not a union),
34 : with a discriminator to track which address type is active.
35 :
36 : @par Thread Safety
37 : Distinct objects: Safe.@n
38 : Shared objects: Safe.
39 :
40 : @par Example
41 : @code
42 : // IPv4 endpoint
43 : endpoint ep4(ipv4_address::loopback(), 8080);
44 :
45 : // IPv6 endpoint
46 : endpoint ep6(ipv6_address::loopback(), 8080);
47 :
48 : // Port only (defaults to IPv4 any address)
49 : endpoint bind_addr(8080);
50 :
51 : // Create from string
52 : auto [ec, ep] = make_endpoint("192.168.1.1:8080");
53 : if (ec)
54 : return;
55 : @endcode
56 : */
57 : class endpoint
58 : {
59 : ipv4_address v4_address_;
60 : ipv6_address v6_address_;
61 : std::uint16_t port_ = 0;
62 : bool is_v4_ = true;
63 :
64 : public:
65 : /** Default constructor.
66 :
67 : Creates an endpoint with the IPv4 any address (0.0.0.0) and port 0.
68 : */
69 HIT 225262 : endpoint() noexcept
70 225262 : : v4_address_(ipv4_address::any())
71 225262 : , v6_address_{}
72 225262 : , port_(0)
73 225262 : , is_v4_(true)
74 : {
75 225262 : }
76 :
77 : /** Construct from IPv4 address and port.
78 :
79 : @param addr The IPv4 address.
80 : @param p The port number in host byte order.
81 : */
82 22184 : endpoint(ipv4_address addr, std::uint16_t p) noexcept
83 22184 : : v4_address_(addr)
84 22184 : , v6_address_{}
85 22184 : , port_(p)
86 22184 : , is_v4_(true)
87 : {
88 22184 : }
89 :
90 : /** Construct from IPv6 address and port.
91 :
92 : @param addr The IPv6 address.
93 : @param p The port number in host byte order.
94 : */
95 152 : endpoint(ipv6_address addr, std::uint16_t p) noexcept
96 152 : : v4_address_(ipv4_address::any())
97 152 : , v6_address_(addr)
98 152 : , port_(p)
99 152 : , is_v4_(false)
100 : {
101 152 : }
102 :
103 : /** Construct from port only.
104 :
105 : Uses the IPv4 any address (0.0.0.0), which binds to all
106 : available network interfaces.
107 :
108 : @param p The port number in host byte order.
109 : */
110 20 : explicit endpoint(std::uint16_t p) noexcept
111 20 : : v4_address_(ipv4_address::any())
112 20 : , v6_address_{}
113 20 : , port_(p)
114 20 : , is_v4_(true)
115 : {
116 20 : }
117 :
118 : /** Construct from an endpoint's address with a different port.
119 :
120 : Creates a new endpoint using the address from an existing
121 : endpoint but with a different port number.
122 :
123 : @param ep The endpoint whose address to use.
124 : @param p The port number in host byte order.
125 : */
126 2 : endpoint(endpoint const& ep, std::uint16_t p) noexcept
127 2 : : v4_address_(ep.v4_address_)
128 2 : , v6_address_(ep.v6_address_)
129 2 : , port_(p)
130 2 : , is_v4_(ep.is_v4_)
131 : {
132 2 : }
133 :
134 : /** Construct from a string.
135 :
136 : Parses an endpoint string in one of the following formats:
137 : @li IPv4 without port: `192.168.1.1`
138 : @li IPv4 with port: `192.168.1.1:8080`
139 : @li IPv6 without port: `::1` or `2001:db8::1`
140 : @li IPv6 with port (bracketed): `[::1]:8080`
141 :
142 : @param s The string to parse.
143 :
144 : @throws std::system_error on parse failure.
145 :
146 : @see make_endpoint for the non-throwing form.
147 : */
148 : explicit endpoint(std::string_view s);
149 :
150 : /** Check if this endpoint uses an IPv4 address.
151 :
152 : @return `true` if the endpoint uses IPv4, `false` if IPv6.
153 : */
154 14967 : bool is_v4() const noexcept
155 : {
156 14967 : return is_v4_;
157 : }
158 :
159 : /** Check if this endpoint uses an IPv6 address.
160 :
161 : @return `true` if the endpoint uses IPv6, `false` if IPv4.
162 : */
163 174 : bool is_v6() const noexcept
164 : {
165 174 : return !is_v4_;
166 : }
167 :
168 : /** Get the IPv4 address.
169 :
170 : @return The IPv4 address. The value is valid even if
171 : the endpoint is using IPv6 (it will be the default any address).
172 : */
173 7652 : ipv4_address v4_address() const noexcept
174 : {
175 7652 : return v4_address_;
176 : }
177 :
178 : /** Get the IPv6 address.
179 :
180 : @return The IPv6 address. The value is valid even if
181 : the endpoint is using IPv4 (it will be the default any address).
182 : */
183 64 : ipv6_address v6_address() const noexcept
184 : {
185 64 : return v6_address_;
186 : }
187 :
188 : /** Get the port number.
189 :
190 : @return The port number in host byte order.
191 : */
192 8152 : std::uint16_t port() const noexcept
193 : {
194 8152 : return port_;
195 : }
196 :
197 : /** Compare endpoints for equality.
198 :
199 : Two endpoints are equal if they have the same address type,
200 : the same address value, and the same port.
201 :
202 : @return `true` if both endpoints are equal.
203 : */
204 93 : friend bool operator==(endpoint const& a, endpoint const& b) noexcept
205 : {
206 93 : if (a.is_v4_ != b.is_v4_)
207 1 : return false;
208 92 : if (a.port_ != b.port_)
209 3 : return false;
210 89 : if (a.is_v4_)
211 89 : return a.v4_address_ == b.v4_address_;
212 : else
213 MIS 0 : return a.v6_address_ == b.v6_address_;
214 : }
215 :
216 : /** Order two endpoints.
217 :
218 : Establishes a strict total ordering consistent with
219 : @ref operator==: equal endpoints compare equivalent.
220 : Endpoints are ordered first by address family (IPv4
221 : before IPv6), then by address value, then by port. This
222 : makes `endpoint` usable as a key in ordered containers
223 : such as `std::map` and `std::set`.
224 :
225 : @return The relative order of @p a and @p b.
226 : */
227 : friend std::strong_ordering
228 HIT 25 : operator<=>(endpoint const& a, endpoint const& b) noexcept
229 : {
230 25 : if (a.is_v4_ != b.is_v4_)
231 9 : return a.is_v4_ ? std::strong_ordering::less
232 9 : : std::strong_ordering::greater;
233 16 : if (a.is_v4_)
234 : {
235 13 : if (auto c = a.v4_address_.to_uint() <=> b.v4_address_.to_uint();
236 13 : c != 0)
237 2 : return c;
238 : }
239 : else
240 : {
241 3 : if (auto c = a.v6_address_.to_bytes() <=> b.v6_address_.to_bytes();
242 3 : c != 0)
243 1 : return c;
244 : }
245 13 : return a.port_ <=> b.port_;
246 : }
247 : };
248 :
249 : /** Endpoint format detection result.
250 :
251 : Used internally by make_endpoint to determine
252 : the format of an endpoint string.
253 : */
254 : enum class endpoint_format
255 : {
256 : ipv4_no_port, ///< "192.168.1.1"
257 : ipv4_with_port, ///< "192.168.1.1:8080"
258 : ipv6_no_port, ///< "::1" or "1:2:3:4:5:6:7:8"
259 : ipv6_bracketed ///< "[::1]" or "[::1]:8080"
260 : };
261 :
262 : /** Detect the format of an endpoint string.
263 :
264 : This helper function determines the endpoint format
265 : based on simple rules:
266 : 1. Starts with `[` -> `ipv6_bracketed`
267 : 2. Else count `:` characters:
268 : - 0 colons -> `ipv4_no_port`
269 : - 1 colon -> `ipv4_with_port`
270 : - 2+ colons -> `ipv6_no_port`
271 :
272 : @param s The string to analyze.
273 : @return The detected endpoint format.
274 : */
275 : BOOST_COROSIO_DECL
276 : endpoint_format detect_endpoint_format(std::string_view s) noexcept;
277 :
278 : /** Create an endpoint from a string.
279 :
280 : This function parses an endpoint string in one of
281 : the following formats:
282 :
283 : @li IPv4 without port: `192.168.1.1`
284 : @li IPv4 with port: `192.168.1.1:8080`
285 : @li IPv6 without port: `::1` or `2001:db8::1`
286 : @li IPv6 with port (bracketed): `[::1]:8080`
287 :
288 : @par Example
289 : @code
290 : auto [ec, ep] = make_endpoint("192.168.1.1:8080");
291 : if (ec)
292 : return;
293 : assert( ep.is_v4() && ep.port() == 8080 );
294 :
295 : auto [ec6, ep6] = make_endpoint("[::1]:443");
296 : if (ec6)
297 : return;
298 : assert( ep6.is_v6() && ep6.port() == 443 );
299 : @endcode
300 :
301 : @param s The string to parse.
302 : @return The error code, empty on success, and the parsed
303 : endpoint — default-constructed on failure.
304 : */
305 : [[nodiscard]] BOOST_COROSIO_DECL capy::io_result<endpoint>
306 : make_endpoint(std::string_view s) noexcept;
307 :
308 25 : inline endpoint::endpoint(std::string_view s)
309 : {
310 25 : auto [ec, ep] = make_endpoint(s);
311 25 : if (ec)
312 16 : detail::throw_system_error(ec);
313 9 : *this = ep;
314 9 : }
315 :
316 : } // namespace boost::corosio
317 :
318 : #endif
|