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