libs/corosio/include/boost/corosio/resolver.hpp

97.4% Lines (75/77) 100.0% Functions (24/24) 70.0% Branches (7/10)
libs/corosio/include/boost/corosio/resolver.hpp
Line Branch Hits Source Code
1 //
2 // Copyright (c) 2025 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_RESOLVER_HPP
11 #define BOOST_COROSIO_RESOLVER_HPP
12
13 #include <boost/corosio/detail/config.hpp>
14 #include <boost/corosio/detail/except.hpp>
15 #include <boost/corosio/endpoint.hpp>
16 #include <boost/corosio/io_object.hpp>
17 #include <boost/capy/io_result.hpp>
18 #include <boost/corosio/resolver_results.hpp>
19 #include <boost/capy/ex/executor_ref.hpp>
20 #include <boost/capy/ex/execution_context.hpp>
21 #include <boost/capy/ex/io_env.hpp>
22 #include <boost/capy/concept/executor.hpp>
23
24 #include <system_error>
25
26 #include <cassert>
27 #include <concepts>
28 #include <coroutine>
29 #include <cstdint>
30 #include <stop_token>
31 #include <string>
32 #include <string_view>
33 #include <type_traits>
34
35 namespace boost::corosio {
36
37 /** Bitmask flags for resolver queries.
38
39 These flags correspond to the hints parameter of getaddrinfo.
40 */
41 enum class resolve_flags : unsigned int
42 {
43 /// No flags.
44 none = 0,
45
46 /// Indicate that returned endpoint is intended for use as a locally
47 /// bound socket endpoint.
48 passive = 0x01,
49
50 /// Host name should be treated as a numeric string defining an IPv4
51 /// or IPv6 address and no name resolution should be attempted.
52 numeric_host = 0x04,
53
54 /// Service name should be treated as a numeric string defining a port
55 /// number and no name resolution should be attempted.
56 numeric_service = 0x08,
57
58 /// Only return IPv4 addresses if a non-loopback IPv4 address is
59 /// configured for the system. Only return IPv6 addresses if a
60 /// non-loopback IPv6 address is configured for the system.
61 address_configured = 0x20,
62
63 /// If the query protocol family is specified as IPv6, return
64 /// IPv4-mapped IPv6 addresses on finding no IPv6 addresses.
65 v4_mapped = 0x800,
66
67 /// If used with v4_mapped, return all matching IPv6 and IPv4 addresses.
68 all_matching = 0x100
69 };
70
71 /** Combine two resolve_flags. */
72 inline
73 resolve_flags
74 10 operator|(resolve_flags a, resolve_flags b) noexcept
75 {
76 return static_cast<resolve_flags>(
77 static_cast<unsigned int>(a) |
78 10 static_cast<unsigned int>(b));
79 }
80
81 /** Combine two resolve_flags. */
82 inline
83 resolve_flags&
84 1 operator|=(resolve_flags& a, resolve_flags b) noexcept
85 {
86 1 a = a | b;
87 1 return a;
88 }
89
90 /** Intersect two resolve_flags. */
91 inline
92 resolve_flags
93 103 operator&(resolve_flags a, resolve_flags b) noexcept
94 {
95 return static_cast<resolve_flags>(
96 static_cast<unsigned int>(a) &
97 103 static_cast<unsigned int>(b));
98 }
99
100 /** Intersect two resolve_flags. */
101 inline
102 resolve_flags&
103 1 operator&=(resolve_flags& a, resolve_flags b) noexcept
104 {
105 1 a = a & b;
106 1 return a;
107 }
108
109 //------------------------------------------------------------------------------
110
111 /** Bitmask flags for reverse resolver queries.
112
113 These flags correspond to the flags parameter of getnameinfo.
114 */
115 enum class reverse_flags : unsigned int
116 {
117 /// No flags.
118 none = 0,
119
120 /// Return the numeric form of the hostname instead of its name.
121 numeric_host = 0x01,
122
123 /// Return the numeric form of the service name instead of its name.
124 numeric_service = 0x02,
125
126 /// Return an error if the hostname cannot be resolved.
127 name_required = 0x04,
128
129 /// Lookup for datagram (UDP) service instead of stream (TCP).
130 datagram_service = 0x08
131 };
132
133 /** Combine two reverse_flags. */
134 inline
135 reverse_flags
136 6 operator|(reverse_flags a, reverse_flags b) noexcept
137 {
138 return static_cast<reverse_flags>(
139 static_cast<unsigned int>(a) |
140 6 static_cast<unsigned int>(b));
141 }
142
143 /** Combine two reverse_flags. */
144 inline
145 reverse_flags&
146 1 operator|=(reverse_flags& a, reverse_flags b) noexcept
147 {
148 1 a = a | b;
149 1 return a;
150 }
151
152 /** Intersect two reverse_flags. */
153 inline
154 reverse_flags
155 47 operator&(reverse_flags a, reverse_flags b) noexcept
156 {
157 return static_cast<reverse_flags>(
158 static_cast<unsigned int>(a) &
159 47 static_cast<unsigned int>(b));
160 }
161
162 /** Intersect two reverse_flags. */
163 inline
164 reverse_flags&
165 1 operator&=(reverse_flags& a, reverse_flags b) noexcept
166 {
167 1 a = a & b;
168 1 return a;
169 }
170
171 //------------------------------------------------------------------------------
172
173 /** An asynchronous DNS resolver for coroutine I/O.
174
175 This class provides asynchronous DNS resolution operations that return
176 awaitable types. Each operation participates in the affine awaitable
177 protocol, ensuring coroutines resume on the correct executor.
178
179 @par Thread Safety
180 Distinct objects: Safe.@n
181 Shared objects: Unsafe. A resolver must not have concurrent resolve
182 operations.
183
184 @par Semantics
185 Wraps platform DNS resolution (getaddrinfo/getnameinfo).
186 Operations dispatch to OS resolver APIs via the io_context
187 thread pool.
188
189 @par Example
190 @code
191 io_context ioc;
192 resolver r(ioc);
193
194 // Using structured bindings
195 auto [ec, results] = co_await r.resolve("www.example.com", "https");
196 if (ec)
197 co_return;
198
199 for (auto const& entry : results)
200 std::cout << entry.get_endpoint().port() << std::endl;
201
202 // Or using exceptions
203 auto results = (co_await r.resolve("www.example.com", "https")).value();
204 @endcode
205 */
206 class BOOST_COROSIO_DECL resolver : public io_object
207 {
208 struct resolve_awaitable
209 {
210 resolver& r_;
211 std::string host_;
212 std::string service_;
213 resolve_flags flags_;
214 std::stop_token token_;
215 mutable std::error_code ec_;
216 mutable resolver_results results_;
217
218 16 resolve_awaitable(
219 resolver& r,
220 std::string_view host,
221 std::string_view service,
222 resolve_flags flags) noexcept
223 16 : r_(r)
224 32 , host_(host)
225 32 , service_(service)
226 16 , flags_(flags)
227 {
228 16 }
229
230 16 bool await_ready() const noexcept
231 {
232 16 return token_.stop_requested();
233 }
234
235 16 capy::io_result<resolver_results> await_resume() const noexcept
236 {
237
1/2
✗ Branch 1 not taken.
✓ Branch 2 taken 16 times.
16 if (token_.stop_requested())
238 return {make_error_code(std::errc::operation_canceled), {}};
239 16 return {ec_, std::move(results_)};
240 16 }
241
242 16 auto await_suspend(
243 std::coroutine_handle<> h,
244 capy::io_env const* env) -> std::coroutine_handle<>
245 {
246 16 token_ = env->stop_token;
247
1/1
✓ Branch 5 taken 16 times.
16 return r_.get().resolve(h, env->executor, host_, service_, flags_, token_, &ec_, &results_);
248 }
249 };
250
251 struct reverse_resolve_awaitable
252 {
253 resolver& r_;
254 endpoint ep_;
255 reverse_flags flags_;
256 std::stop_token token_;
257 mutable std::error_code ec_;
258 mutable reverse_resolver_result result_;
259
260 10 reverse_resolve_awaitable(
261 resolver& r,
262 endpoint const& ep,
263 reverse_flags flags) noexcept
264 10 : r_(r)
265 10 , ep_(ep)
266 10 , flags_(flags)
267 {
268 10 }
269
270 10 bool await_ready() const noexcept
271 {
272 10 return token_.stop_requested();
273 }
274
275 10 capy::io_result<reverse_resolver_result> await_resume() const noexcept
276 {
277
1/2
✗ Branch 1 not taken.
✓ Branch 2 taken 10 times.
10 if (token_.stop_requested())
278 return {make_error_code(std::errc::operation_canceled), {}};
279 10 return {ec_, std::move(result_)};
280 10 }
281
282 10 auto await_suspend(
283 std::coroutine_handle<> h,
284 capy::io_env const* env) -> std::coroutine_handle<>
285 {
286 10 token_ = env->stop_token;
287
1/1
✓ Branch 3 taken 10 times.
10 return r_.get().reverse_resolve(h, env->executor, ep_, flags_, token_, &ec_, &result_);
288 }
289 };
290
291 public:
292 /** Destructor.
293
294 Cancels any pending operations.
295 */
296 ~resolver();
297
298 /** Construct a resolver from an execution context.
299
300 @param ctx The execution context that will own this resolver.
301 */
302 explicit resolver(capy::execution_context& ctx);
303
304 /** Construct a resolver from an executor.
305
306 The resolver is associated with the executor's context.
307
308 @param ex The executor whose context will own the resolver.
309 */
310 template<class Ex>
311 requires (!std::same_as<std::remove_cvref_t<Ex>, resolver>) &&
312 capy::Executor<Ex>
313 1 explicit resolver(Ex const& ex)
314 1 : resolver(ex.context())
315 {
316 1 }
317
318 /** Move constructor.
319
320 Transfers ownership of the resolver resources.
321
322 @param other The resolver to move from.
323 */
324 1 resolver(resolver&& other) noexcept
325 1 : io_object(other.context())
326 {
327 1 impl_ = other.impl_;
328 1 other.impl_ = nullptr;
329 1 }
330
331 /** Move assignment operator.
332
333 Cancels any existing operations and transfers ownership.
334 The source and destination must share the same execution context.
335
336 @param other The resolver to move from.
337
338 @return Reference to this resolver.
339
340 @throws std::logic_error if the resolvers have different
341 execution contexts.
342 */
343 2 resolver& operator=(resolver&& other)
344 {
345
1/2
✓ Branch 0 taken 2 times.
✗ Branch 1 not taken.
2 if (this != &other)
346 {
347
2/2
✓ Branch 0 taken 1 time.
✓ Branch 1 taken 1 time.
2 if (ctx_ != other.ctx_)
348 1 detail::throw_logic_error(
349 "cannot move resolver across execution contexts");
350 1 cancel();
351 1 impl_ = other.impl_;
352 1 other.impl_ = nullptr;
353 }
354 1 return *this;
355 }
356
357 resolver(resolver const&) = delete;
358 resolver& operator=(resolver const&) = delete;
359
360 /** Initiate an asynchronous resolve operation.
361
362 Resolves the host and service names into a list of endpoints.
363
364 @param host A string identifying a location. May be a descriptive
365 name or a numeric address string.
366
367 @param service A string identifying the requested service. This may
368 be a descriptive name or a numeric string corresponding to a
369 port number.
370
371 @return An awaitable that completes with `io_result<resolver_results>`.
372
373 @par Example
374 @code
375 auto [ec, results] = co_await r.resolve("www.example.com", "https");
376 @endcode
377 */
378 5 auto resolve(
379 std::string_view host,
380 std::string_view service)
381 {
382 5 return resolve_awaitable(*this, host, service, resolve_flags::none);
383 }
384
385 /** Initiate an asynchronous resolve operation with flags.
386
387 Resolves the host and service names into a list of endpoints.
388
389 @param host A string identifying a location.
390
391 @param service A string identifying the requested service.
392
393 @param flags Flags controlling resolution behavior.
394
395 @return An awaitable that completes with `io_result<resolver_results>`.
396 */
397 11 auto resolve(
398 std::string_view host,
399 std::string_view service,
400 resolve_flags flags)
401 {
402 11 return resolve_awaitable(*this, host, service, flags);
403 }
404
405 /** Initiate an asynchronous reverse resolve operation.
406
407 Resolves an endpoint into a hostname and service name using
408 reverse DNS lookup (PTR record query).
409
410 @param ep The endpoint to resolve.
411
412 @return An awaitable that completes with
413 `io_result<reverse_resolver_result>`.
414
415 @par Example
416 @code
417 endpoint ep(ipv4_address({127, 0, 0, 1}), 80);
418 auto [ec, result] = co_await r.resolve(ep);
419 if (!ec)
420 std::cout << result.host_name() << ":" << result.service_name();
421 @endcode
422 */
423 3 auto resolve(endpoint const& ep)
424 {
425 3 return reverse_resolve_awaitable(*this, ep, reverse_flags::none);
426 }
427
428 /** Initiate an asynchronous reverse resolve operation with flags.
429
430 Resolves an endpoint into a hostname and service name using
431 reverse DNS lookup (PTR record query).
432
433 @param ep The endpoint to resolve.
434
435 @param flags Flags controlling resolution behavior. See reverse_flags.
436
437 @return An awaitable that completes with
438 `io_result<reverse_resolver_result>`.
439 */
440 7 auto resolve(endpoint const& ep, reverse_flags flags)
441 {
442 7 return reverse_resolve_awaitable(*this, ep, flags);
443 }
444
445 /** Cancel any pending asynchronous operations.
446
447 All outstanding operations complete with `errc::operation_canceled`.
448 Check `ec == cond::canceled` for portable comparison.
449 */
450 void cancel();
451
452 public:
453 struct resolver_impl : io_object_impl
454 {
455 virtual std::coroutine_handle<> resolve(
456 std::coroutine_handle<>,
457 capy::executor_ref,
458 std::string_view host,
459 std::string_view service,
460 resolve_flags flags,
461 std::stop_token,
462 std::error_code*,
463 resolver_results*) = 0;
464
465 virtual std::coroutine_handle<> reverse_resolve(
466 std::coroutine_handle<>,
467 capy::executor_ref,
468 endpoint const& ep,
469 reverse_flags flags,
470 std::stop_token,
471 std::error_code*,
472 reverse_resolver_result*) = 0;
473
474 virtual void cancel() noexcept = 0;
475 };
476
477 private:
478 31 inline resolver_impl& get() const noexcept
479 {
480 31 return *static_cast<resolver_impl*>(impl_);
481 }
482 };
483
484 } // namespace boost::corosio
485
486 #endif
487