LCOV - code coverage report
Current view: top level - include/boost/corosio - resolver.hpp (source / functions) Coverage Total Hit
Test: coverage_remapped.info Lines: 97.3 % 73 71
Test Date: 2026-02-15 04:10:46 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(std::move(other))
     326              :     {
     327            1 :     }
     328              : 
     329              :     /** Move assignment operator.
     330              : 
     331              :         Cancels any existing operations and transfers ownership.
     332              :         The source and destination must share the same execution context.
     333              : 
     334              :         @param other The resolver to move from.
     335              : 
     336              :         @return Reference to this resolver.
     337              : 
     338              :         @throws std::logic_error if the resolvers have different
     339              :             execution contexts.
     340              :     */
     341            2 :     resolver& operator=(resolver&& other)
     342              :     {
     343            2 :         if (this != &other)
     344              :         {
     345            2 :             if (&context() != &other.context())
     346            1 :                 detail::throw_logic_error(
     347              :                     "cannot move resolver across execution contexts");
     348            1 :             h_ = std::move(other.h_);
     349              :         }
     350            1 :         return *this;
     351              :     }
     352              : 
     353              :     resolver(resolver const&) = delete;
     354              :     resolver& operator=(resolver const&) = delete;
     355              : 
     356              :     /** Initiate an asynchronous resolve operation.
     357              : 
     358              :         Resolves the host and service names into a list of endpoints.
     359              : 
     360              :         @param host A string identifying a location. May be a descriptive
     361              :             name or a numeric address string.
     362              : 
     363              :         @param service A string identifying the requested service. This may
     364              :             be a descriptive name or a numeric string corresponding to a
     365              :             port number.
     366              : 
     367              :         @return An awaitable that completes with `io_result<resolver_results>`.
     368              : 
     369              :         @par Example
     370              :         @code
     371              :         auto [ec, results] = co_await r.resolve("www.example.com", "https");
     372              :         @endcode
     373              :     */
     374            5 :     auto resolve(
     375              :         std::string_view host,
     376              :         std::string_view service)
     377              :     {
     378            5 :         return resolve_awaitable(*this, host, service, resolve_flags::none);
     379              :     }
     380              : 
     381              :     /** Initiate an asynchronous resolve operation with flags.
     382              : 
     383              :         Resolves the host and service names into a list of endpoints.
     384              : 
     385              :         @param host A string identifying a location.
     386              : 
     387              :         @param service A string identifying the requested service.
     388              : 
     389              :         @param flags Flags controlling resolution behavior.
     390              : 
     391              :         @return An awaitable that completes with `io_result<resolver_results>`.
     392              :     */
     393           11 :     auto resolve(
     394              :         std::string_view host,
     395              :         std::string_view service,
     396              :         resolve_flags flags)
     397              :     {
     398           11 :         return resolve_awaitable(*this, host, service, flags);
     399              :     }
     400              : 
     401              :     /** Initiate an asynchronous reverse resolve operation.
     402              : 
     403              :         Resolves an endpoint into a hostname and service name using
     404              :         reverse DNS lookup (PTR record query).
     405              : 
     406              :         @param ep The endpoint to resolve.
     407              : 
     408              :         @return An awaitable that completes with
     409              :             `io_result<reverse_resolver_result>`.
     410              : 
     411              :         @par Example
     412              :         @code
     413              :         endpoint ep(ipv4_address({127, 0, 0, 1}), 80);
     414              :         auto [ec, result] = co_await r.resolve(ep);
     415              :         if (!ec)
     416              :             std::cout << result.host_name() << ":" << result.service_name();
     417              :         @endcode
     418              :     */
     419            3 :     auto resolve(endpoint const& ep)
     420              :     {
     421            3 :         return reverse_resolve_awaitable(*this, ep, reverse_flags::none);
     422              :     }
     423              : 
     424              :     /** Initiate an asynchronous reverse resolve operation with flags.
     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              :         @param flags Flags controlling resolution behavior. See reverse_flags.
     432              : 
     433              :         @return An awaitable that completes with
     434              :             `io_result<reverse_resolver_result>`.
     435              :     */
     436            7 :     auto resolve(endpoint const& ep, reverse_flags flags)
     437              :     {
     438            7 :         return reverse_resolve_awaitable(*this, ep, flags);
     439              :     }
     440              : 
     441              :     /** Cancel any pending asynchronous operations.
     442              : 
     443              :         All outstanding operations complete with `errc::operation_canceled`.
     444              :         Check `ec == cond::canceled` for portable comparison.
     445              :     */
     446              :     void cancel();
     447              : 
     448              : public:
     449              :     struct implementation : io_object::implementation
     450              :     {
     451              :         virtual std::coroutine_handle<> resolve(
     452              :             std::coroutine_handle<>,
     453              :             capy::executor_ref,
     454              :             std::string_view host,
     455              :             std::string_view service,
     456              :             resolve_flags flags,
     457              :             std::stop_token,
     458              :             std::error_code*,
     459              :             resolver_results*) = 0;
     460              : 
     461              :         virtual std::coroutine_handle<> reverse_resolve(
     462              :             std::coroutine_handle<>,
     463              :             capy::executor_ref,
     464              :             endpoint const& ep,
     465              :             reverse_flags flags,
     466              :             std::stop_token,
     467              :             std::error_code*,
     468              :             reverse_resolver_result*) = 0;
     469              : 
     470              :         virtual void cancel() noexcept = 0;
     471              :     };
     472              : 
     473              : private:
     474           30 :     inline implementation& get() const noexcept
     475              :     {
     476           30 :         return *static_cast<implementation*>(h_.get());
     477              :     }
     478              : };
     479              : 
     480              : } // namespace boost::corosio
     481              : 
     482              : #endif
        

Generated by: LCOV version 2.3