LCOV - code coverage report
Current view: top level - include/boost/corosio - resolver.hpp (source / functions) Coverage Total Hit
Test: coverage_remapped.info Lines: 97.4 % 77 75
Test Date: 2026-02-12 20:11:43 Functions: 100.0 % 24 24

            Line data    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           16 :             if (token_.stop_requested())
     238            0 :                 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           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           10 :             if (token_.stop_requested())
     278            0 :                 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           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            2 :         if (this != &other)
     346              :         {
     347            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
        

Generated by: LCOV version 2.3