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