include/boost/corosio/resolver.hpp

96.2% Lines (77 / 80) 100.0% Functions (25 / 25)
resolver.hpp
f(x) Functions (25)
Function Calls Lines Blocks
boost::corosio::operator|(boost::corosio::resolve_flags, boost::corosio::resolve_flags) :74 17x 100.0% 100.0% boost::corosio::operator|=(boost::corosio::resolve_flags&, boost::corosio::resolve_flags) :82 1x 100.0% 100.0% boost::corosio::operator&(boost::corosio::resolve_flags, boost::corosio::resolve_flags) :90 205x 100.0% 100.0% boost::corosio::operator&=(boost::corosio::resolve_flags&, boost::corosio::resolve_flags) :98 1x 100.0% 100.0% boost::corosio::operator|(boost::corosio::reverse_flags, boost::corosio::reverse_flags) :128 9x 100.0% 100.0% boost::corosio::operator|=(boost::corosio::reverse_flags&, boost::corosio::reverse_flags) :136 1x 100.0% 100.0% boost::corosio::operator&(boost::corosio::reverse_flags, boost::corosio::reverse_flags) :144 75x 100.0% 100.0% boost::corosio::operator&=(boost::corosio::reverse_flags&, boost::corosio::reverse_flags) :152 1x 100.0% 100.0% boost::corosio::resolver::resolve_awaitable::resolve_awaitable(boost::corosio::resolver&, std::basic_string_view<char, std::char_traits<char> >, std::basic_string_view<char, std::char_traits<char> >, boost::corosio::resolve_flags) :201 29x 100.0% 100.0% boost::corosio::resolver::resolve_awaitable::dispatch(std::__n4861::coroutine_handle<void>, boost::capy::executor_ref) const :220 28x 100.0% 83.0% boost::corosio::resolver::resolve_host_awaitable::resolve_host_awaitable(boost::corosio::resolver&, std::basic_string_view<char, std::char_traits<char> >, boost::corosio::resolve_flags) :233 6x 100.0% 100.0% boost::corosio::resolver::resolve_host_awaitable::dispatch(std::__n4861::coroutine_handle<void>, boost::capy::executor_ref) const :248 5x 100.0% 77.0% boost::corosio::resolver::resolve_host_awaitable::await_resume() const :260 6x 78.6% 70.0% boost::corosio::resolver::reverse_resolve_awaitable::reverse_resolve_awaitable(boost::corosio::resolver&, boost::corosio::endpoint const&, boost::corosio::reverse_flags) :292 20x 100.0% 100.0% boost::corosio::resolver::reverse_resolve_awaitable::dispatch(std::__n4861::coroutine_handle<void>, boost::capy::executor_ref) const :307 19x 100.0% 80.0% boost::corosio::resolver::resolver<boost::corosio::io_context::executor_type>(boost::corosio::io_context::executor_type const&) :336 1x 100.0% 100.0% boost::corosio::resolver::resolver(boost::corosio::resolver&&) :353 2x 100.0% 100.0% boost::corosio::resolver::operator=(boost::corosio::resolver&&) :370 2x 100.0% 100.0% boost::corosio::resolver::resolve(std::basic_string_view<char, std::char_traits<char> >, std::basic_string_view<char, std::char_traits<char> >) :401 13x 100.0% 100.0% boost::corosio::resolver::resolve(std::basic_string_view<char, std::char_traits<char> >) :425 3x 100.0% 100.0% boost::corosio::resolver::resolve(std::basic_string_view<char, std::char_traits<char> >, boost::corosio::resolve_flags) :438 3x 100.0% 100.0% boost::corosio::resolver::resolve(std::basic_string_view<char, std::char_traits<char> >, std::basic_string_view<char, std::char_traits<char> >, boost::corosio::resolve_flags) :458 16x 100.0% 100.0% boost::corosio::resolver::resolve(boost::corosio::endpoint const&) :479 11x 100.0% 100.0% boost::corosio::resolver::resolve(boost::corosio::endpoint const&, boost::corosio::reverse_flags) :498 9x 100.0% 100.0% boost::corosio::resolver::get() const :576 59x 100.0% 100.0%
Line TLA Hits Source Code
1 //
2 // Copyright (c) 2025 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_RESOLVER_HPP
13 #define BOOST_COROSIO_RESOLVER_HPP
14
15 #include <boost/corosio/detail/config.hpp>
16 #include <boost/corosio/detail/op_base.hpp>
17 #include <boost/corosio/endpoint.hpp>
18 #include <boost/corosio/io/io_object.hpp>
19 #include <boost/capy/io_result.hpp>
20 #include <boost/capy/ex/executor_ref.hpp>
21 #include <boost/capy/ex/execution_context.hpp>
22 #include <boost/capy/ex/io_env.hpp>
23 #include <boost/capy/concept/executor.hpp>
24
25 #include <system_error>
26
27 #include <cassert>
28 #include <concepts>
29 #include <coroutine>
30 #include <stop_token>
31 #include <string>
32 #include <string_view>
33 #include <vector>
34 #include <type_traits>
35
36 namespace boost::corosio {
37
38 /** Bitmask flags for resolver queries.
39
40 These flags correspond to the hints parameter of `getaddrinfo`.
41 */
42 enum class resolve_flags : unsigned int
43 {
44 /// No flags.
45 none = 0,
46
47 /// Indicate that returned endpoint is intended for use as a locally
48 /// bound socket endpoint.
49 passive = 0x01,
50
51 /// Host name should be treated as a numeric string defining an IPv4
52 /// or IPv6 address and no name resolution should be attempted.
53 numeric_host = 0x04,
54
55 /// Service name should be treated as a numeric string defining a port
56 /// number and no name resolution should be attempted.
57 numeric_service = 0x08,
58
59 /// Only return IPv4 addresses if a non-loopback IPv4 address is
60 /// configured for the system. Only return IPv6 addresses if a
61 /// non-loopback IPv6 address is configured for the system.
62 address_configured = 0x20,
63
64 /// If the query protocol family is specified as IPv6, return
65 /// IPv4-mapped IPv6 addresses on finding no IPv6 addresses.
66 v4_mapped = 0x800,
67
68 /// If used with v4_mapped, return all matching IPv6 and IPv4 addresses.
69 all_matching = 0x100
70 };
71
72 /** Combine two `resolve_flags`. */
73 inline resolve_flags
74 17x operator|(resolve_flags a, resolve_flags b) noexcept
75 {
76 return static_cast<resolve_flags>(
77 17x static_cast<unsigned int>(a) | static_cast<unsigned int>(b));
78 }
79
80 /** Combine two `resolve_flags`. */
81 inline resolve_flags&
82 1x operator|=(resolve_flags& a, resolve_flags b) noexcept
83 {
84 1x a = a | b;
85 1x return a;
86 }
87
88 /** Intersect two `resolve_flags`. */
89 inline resolve_flags
90 205x operator&(resolve_flags a, resolve_flags b) noexcept
91 {
92 return static_cast<resolve_flags>(
93 205x static_cast<unsigned int>(a) & static_cast<unsigned int>(b));
94 }
95
96 /** Intersect two `resolve_flags`. */
97 inline resolve_flags&
98 1x operator&=(resolve_flags& a, resolve_flags b) noexcept
99 {
100 1x a = a & b;
101 1x return a;
102 }
103
104 /** Bitmask flags for reverse resolver queries.
105
106 These flags correspond to the flags parameter of `getnameinfo`.
107 */
108 enum class reverse_flags : unsigned int
109 {
110 /// No flags.
111 none = 0,
112
113 /// Return the numeric form of the hostname instead of its name.
114 numeric_host = 0x01,
115
116 /// Return the numeric form of the service name instead of its name.
117 numeric_service = 0x02,
118
119 /// Return an error if the hostname cannot be resolved.
120 name_required = 0x04,
121
122 /// Lookup for datagram (UDP) service instead of stream (TCP).
123 datagram_service = 0x08
124 };
125
126 /** Combine two `reverse_flags`. */
127 inline reverse_flags
128 9x operator|(reverse_flags a, reverse_flags b) noexcept
129 {
130 return static_cast<reverse_flags>(
131 9x static_cast<unsigned int>(a) | static_cast<unsigned int>(b));
132 }
133
134 /** Combine two `reverse_flags`. */
135 inline reverse_flags&
136 1x operator|=(reverse_flags& a, reverse_flags b) noexcept
137 {
138 1x a = a | b;
139 1x return a;
140 }
141
142 /** Intersect two `reverse_flags`. */
143 inline reverse_flags
144 75x operator&(reverse_flags a, reverse_flags b) noexcept
145 {
146 return static_cast<reverse_flags>(
147 75x static_cast<unsigned int>(a) & static_cast<unsigned int>(b));
148 }
149
150 /** Intersect two `reverse_flags`. */
151 inline reverse_flags&
152 1x operator&=(reverse_flags& a, reverse_flags b) noexcept
153 {
154 1x a = a & b;
155 1x return a;
156 }
157
158 /** The name of an endpoint.
159
160 Reverse resolution translates an endpoint into its symbolic
161 spelling: the host name and the service name. Both fields carry
162 resolved data; the endpoint they name is the one the caller
163 passed to `resolve`.
164 */
165 struct endpoint_name
166 {
167 /// The resolved host name.
168 std::string host_name;
169
170 /// The resolved service name.
171 std::string service_name;
172 };
173
174 /** Resolves host names and services to endpoints, from a coroutine.
175
176 This class provides asynchronous DNS resolution operations that return
177 awaitable types. Each operation participates in the affine awaitable
178 protocol, ensuring coroutines resume on the correct executor.
179
180 @par Thread Safety
181 Distinct objects: Safe.@n
182 Shared objects: Unsafe. A resolver must not have concurrent resolve
183 operations.
184
185 @par Semantics
186 Wraps platform DNS resolution (`getaddrinfo`/`getnameinfo`).
187 Operations dispatch to OS resolver APIs via the `io_context`
188 thread pool.
189
190 @par Example
191 @par !example resolver
192 */
193 class BOOST_COROSIO_DECL resolver : public io_object
194 {
195 struct resolve_awaitable
196 : detail::value_op_base<resolve_awaitable, std::vector<endpoint>>
197 {
198 private:
199 friend resolver;
200
201 29x resolve_awaitable(
202 resolver& r,
203 std::string_view host,
204 std::string_view service,
205 resolve_flags flags) noexcept
206 58x : r_(r)
207 58x , host_(host)
208 58x , service_(service)
209 29x , flags_(flags)
210 {
211 29x }
212
213 friend detail::value_op_base<resolve_awaitable, std::vector<endpoint>>;
214 resolver& r_;
215 std::string host_;
216 std::string service_;
217 resolve_flags flags_;
218
219 std::coroutine_handle<>
220 28x dispatch(std::coroutine_handle<> h, capy::executor_ref ex) const
221 {
222 84x return r_.get().resolve(
223 84x h, ex, host_, service_, flags_, token_, &ec_, &value_);
224 }
225 };
226
227 struct resolve_host_awaitable
228 : detail::value_op_base<resolve_host_awaitable, std::vector<endpoint>>
229 {
230 private:
231 friend resolver;
232
233 6x resolve_host_awaitable(
234 resolver& r, std::string_view host, resolve_flags flags) noexcept
235 12x : r_(r)
236 12x , host_(host)
237 6x , flags_(flags)
238 {
239 6x }
240
241 friend detail::
242 value_op_base<resolve_host_awaitable, std::vector<endpoint>>;
243 resolver& r_;
244 std::string host_;
245 resolve_flags flags_;
246
247 std::coroutine_handle<>
248 5x dispatch(std::coroutine_handle<> h, capy::executor_ref ex) const
249 {
250 // An empty service reaches the system resolver as null,
251 // which is the host-only query
252 15x return r_.get().resolve(
253 15x h, ex, host_, {}, flags_, token_, &ec_, &value_);
254 }
255
256 public:
257 // Shadows the base: the endpoint result is reshaped into
258 // the honest address list
259 [[nodiscard]] capy::io_result<std::vector<ip_address>>
260 6x await_resume() const
261 {
262 6x std::vector<ip_address> addrs;
263 6x addrs.reserve(value_.size());
264 9x for (auto const& entry : value_)
265 {
266 3x auto a = entry.address();
267 3x bool duplicate = false;
268 3x for (auto const& seen : addrs)
269 {
270 ✗ if (seen == a)
271 {
272 ✗ duplicate = true;
273 ✗ break;
274 }
275 }
276 // The same address can come back more than once
277 // (mixed name sources, repeated records); each
278 // address is reported once
279 3x if (!duplicate)
280 3x addrs.push_back(a);
281 }
282 12x return {ec_, std::move(addrs)};
283 6x }
284 };
285
286 struct reverse_resolve_awaitable
287 : detail::value_op_base<reverse_resolve_awaitable, endpoint_name>
288 {
289 private:
290 friend resolver;
291
292 20x reverse_resolve_awaitable(
293 resolver& r, endpoint const& ep, reverse_flags flags) noexcept
294 40x : r_(r)
295 20x , ep_(ep)
296 20x , flags_(flags)
297 {
298 20x }
299
300 friend detail::value_op_base<reverse_resolve_awaitable, endpoint_name>;
301
302 resolver& r_;
303 endpoint ep_;
304 reverse_flags flags_;
305
306 std::coroutine_handle<>
307 19x dispatch(std::coroutine_handle<> h, capy::executor_ref ex) const
308 {
309 38x return r_.get().reverse_resolve(
310 38x h, ex, ep_, flags_, token_, &ec_, &value_);
311 }
312 };
313
314 public:
315 /** Destructor.
316
317 Cancels any pending operations.
318 */
319 ~resolver() override;
320
321 /** Construct a resolver from an execution context.
322
323 @param ctx The execution context that owns this resolver.
324 */
325 explicit resolver(capy::execution_context& ctx);
326
327 /** Construct a resolver from an executor.
328
329 The resolver is associated with the executor's context.
330
331 @param ex The executor whose context owns the resolver.
332 */
333 template<class Ex>
334 requires(!std::same_as<std::remove_cvref_t<Ex>, resolver>) &&
335 capy::Executor<Ex>
336 1x explicit resolver(Ex const& ex) : resolver(ex.context())
337 {
338 1x }
339
340 /** Move constructor.
341
342 Transfers ownership of the resolver resources. After the move,
343 @p other is in a moved-from state and may only be destroyed or
344 assigned to.
345
346 @param other The resolver to move from.
347
348 @pre No awaitables returned by @p other's `resolve` methods
349 exist.
350 @pre The execution context associated with @p other must
351 outlive this resolver.
352 */
353 2x resolver(resolver&& other) noexcept : io_object(std::move(other)) {}
354
355 /** Move assignment operator.
356
357 Destroys the current implementation and transfers ownership
358 from @p other. After the move, @p other is in a moved-from
359 state and may only be destroyed or assigned to.
360
361 @param other The resolver to move from.
362
363 @pre No awaitables returned by either `*this` or @p other's
364 `resolve` methods exist.
365 @pre The execution context associated with @p other must
366 outlive this resolver.
367
368 @return Reference to this resolver.
369 */
370 2x resolver& operator=(resolver&& other) noexcept
371 {
372 2x if (this != &other)
373 2x h_ = std::move(other.h_);
374 2x return *this;
375 }
376
377 /// Copy construction is disabled; the handle is uniquely owned.
378 resolver(resolver const&) = delete;
379 /// Copy assignment is disabled; the handle is uniquely owned.
380 resolver& operator=(resolver const&) = delete;
381
382 /** Initiate an asynchronous resolve operation.
383
384 Resolves the host and service names into a list of endpoints.
385
386 This resolver must outlive the returned awaitable.
387
388 @param host A string identifying a location. May be a descriptive
389 name or a numeric address string.
390
391 @param service A string identifying the requested service. This may
392 be a descriptive name or a numeric string corresponding to a
393 port number.
394
395 @return An awaitable that completes with
396 `io_result<std::vector<endpoint>>`.
397
398 @par Example
399 @par !example forward_resolve
400 */
401 13x [[nodiscard]] auto resolve(std::string_view host, std::string_view service)
402 {
403 13x return resolve_awaitable(*this, host, service, resolve_flags::none);
404 }
405
406 /** Initiate an asynchronous host-only resolve operation.
407
408 Resolves a host name into its addresses, with no service or
409 port involved — the query `getaddrinfo` performs with a null
410 service. Use this when the host and port travel separately,
411 as they do in most configuration.
412
413 Each address appears once in the result even when the query
414 reports it more than once, and link-local results keep
415 their zone.
416
417 @param host The host name or numeric address string.
418
419 @return An awaitable that completes with
420 `io_result<std::vector<ip_address>>`.
421
422 @par Example
423 @par !example host_only_resolve
424 */
425 3x [[nodiscard]] auto resolve(std::string_view host)
426 {
427 3x return resolve_host_awaitable(*this, host, resolve_flags::none);
428 }
429
430 /** Initiate an asynchronous host-only resolve operation with flags.
431
432 @param host The host name or numeric address string.
433 @param flags Resolution behavior flags.
434
435 @return An awaitable that completes with
436 `io_result<std::vector<ip_address>>`.
437 */
438 3x [[nodiscard]] auto resolve(std::string_view host, resolve_flags flags)
439 {
440 3x return resolve_host_awaitable(*this, host, flags);
441 }
442
443 /** Initiate an asynchronous resolve operation with flags.
444
445 Resolves the host and service names into a list of endpoints.
446
447 This resolver must outlive the returned awaitable.
448
449 @param host A string identifying a location.
450
451 @param service A string identifying the requested service.
452
453 @param flags Flags controlling resolution behavior.
454
455 @return An awaitable that completes with
456 `io_result<std::vector<endpoint>>`.
457 */
458 16x [[nodiscard]] auto resolve(
459 std::string_view host, std::string_view service, resolve_flags flags)
460 {
461 16x return resolve_awaitable(*this, host, service, flags);
462 }
463
464 /** Initiate an asynchronous reverse resolve operation.
465
466 Resolves an endpoint into a hostname and service name using
467 reverse DNS lookup (PTR record query).
468
469 This resolver must outlive the returned awaitable.
470
471 @param ep The endpoint to resolve.
472
473 @return An awaitable that completes with
474 `io_result<endpoint_name>`.
475
476 @par Example
477 @par !example reverse_resolve
478 */
479 11x [[nodiscard]] auto resolve(endpoint const& ep)
480 {
481 11x return reverse_resolve_awaitable(*this, ep, reverse_flags::none);
482 }
483
484 /** Initiate an asynchronous reverse resolve operation with flags.
485
486 Resolves an endpoint into a hostname and service name using
487 reverse DNS lookup (PTR record query).
488
489 This resolver must outlive the returned awaitable.
490
491 @param ep The endpoint to resolve.
492
493 @param flags Flags controlling resolution behavior. See reverse_flags.
494
495 @return An awaitable that completes with
496 `io_result<endpoint_name>`.
497 */
498 9x [[nodiscard]] auto resolve(endpoint const& ep, reverse_flags flags)
499 {
500 9x return reverse_resolve_awaitable(*this, ep, flags);
501 }
502
503 /** Cancel any pending asynchronous operations.
504
505 A resolve transfers no bytes, so a cancellation always wins. An
506 operation reports `errc::operation_canceled` even when the lookup
507 had already completed when the cancellation landed. Check
508 `ec == cond::canceled` for a portable comparison.
509 */
510 void cancel() noexcept;
511
512 public:
513 /** Define backend hooks for DNS resolution operations.
514
515 Platform backends derive from this to implement forward and
516 reverse DNS resolution via `getaddrinfo`/`getnameinfo`.
517 */
518 struct implementation : io_object::implementation
519 {
520 /** Initiate an asynchronous forward DNS resolution.
521
522 @param h Coroutine handle to resume on completion.
523 @param ex Executor for dispatching the completion.
524 @param host The host name or address literal to resolve.
525 @param service The service name or port number.
526 @param flags Flags controlling the lookup.
527 @param token Stop token for cancellation.
528 @param ec Output error code.
529 @param results Output resolver results.
530
531 @return Coroutine handle to resume immediately.
532 */
533 virtual std::coroutine_handle<> resolve(
534 std::coroutine_handle<> h,
535 capy::executor_ref ex,
536 std::string_view host,
537 std::string_view service,
538 resolve_flags flags,
539 std::stop_token token,
540 std::error_code* ec,
541 std::vector<endpoint>* results) = 0;
542
543 /** Initiate an asynchronous reverse DNS resolution.
544
545 @param h Coroutine handle to resume on completion.
546 @param ex Executor for dispatching the completion.
547 @param ep The endpoint to resolve.
548 @param flags Flags controlling the lookup.
549 @param token Stop token for cancellation.
550 @param ec Output error code.
551 @param result Output reverse-resolution result.
552
553 @return Coroutine handle to resume immediately.
554 */
555 virtual std::coroutine_handle<> reverse_resolve(
556 std::coroutine_handle<> h,
557 capy::executor_ref ex,
558 endpoint const& ep,
559 reverse_flags flags,
560 std::stop_token token,
561 std::error_code* ec,
562 endpoint_name* result) = 0;
563
564 /// Cancel pending resolve operations.
565 virtual void cancel() noexcept = 0;
566 };
567
568 protected:
569 /** Adopt an existing handle.
570
571 @param h The handle the resolver takes ownership of.
572 */
573 explicit resolver(handle h) noexcept : io_object(std::move(h)) {}
574
575 private:
576 59x inline implementation& get() const noexcept
577 {
578 59x return *static_cast<implementation*>(h_.get());
579 }
580 };
581
582 } // namespace boost::corosio
583
584 #endif
585