LCOV - code coverage report
Current view: top level - /jenkins/workspace/boost-root/boost/corosio - resolver.hpp (source / functions) Coverage Total Hit
Test: coverage_filtered.info Lines: 97.4 % 77 75
Test Date: 2026-02-06 05:04:16 Functions: 100.0 % 24 24

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

Generated by: LCOV version 2.3