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.5 % 79 77
Test Date: 2026-02-04 14:16:13 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              :             r_.get().resolve(h, ex, host_, service_, flags_, token_, &ec_, &results_);
     247              :             return std::noop_coroutine();
     248              :         }
     249              : 
     250              :         template<typename Ex>
     251           16 :         auto await_suspend(
     252              :             std::coroutine_handle<> h,
     253              :             Ex const& ex,
     254              :             std::stop_token token) -> std::coroutine_handle<>
     255              :         {
     256           16 :             token_ = std::move(token);
     257           16 :             r_.get().resolve(h, ex, host_, service_, flags_, token_, &ec_, &results_);
     258           16 :             return std::noop_coroutine();
     259              :         }
     260              :     };
     261              : 
     262              :     struct reverse_resolve_awaitable
     263              :     {
     264              :         resolver& r_;
     265              :         endpoint ep_;
     266              :         reverse_flags flags_;
     267              :         std::stop_token token_;
     268              :         mutable std::error_code ec_;
     269              :         mutable reverse_resolver_result result_;
     270              : 
     271           10 :         reverse_resolve_awaitable(
     272              :             resolver& r,
     273              :             endpoint const& ep,
     274              :             reverse_flags flags) noexcept
     275           10 :             : r_(r)
     276           10 :             , ep_(ep)
     277           10 :             , flags_(flags)
     278              :         {
     279           10 :         }
     280              : 
     281           10 :         bool await_ready() const noexcept
     282              :         {
     283           10 :             return token_.stop_requested();
     284              :         }
     285              : 
     286           10 :         capy::io_result<reverse_resolver_result> await_resume() const noexcept
     287              :         {
     288           10 :             if (token_.stop_requested())
     289            0 :                 return {make_error_code(std::errc::operation_canceled), {}};
     290           10 :             return {ec_, std::move(result_)};
     291           10 :         }
     292              : 
     293              :         template<typename Ex>
     294              :         auto await_suspend(
     295              :             std::coroutine_handle<> h,
     296              :             Ex const& ex) -> std::coroutine_handle<>
     297              :         {
     298              :             r_.get().reverse_resolve(h, ex, ep_, flags_, token_, &ec_, &result_);
     299              :             return std::noop_coroutine();
     300              :         }
     301              : 
     302              :         template<typename Ex>
     303           10 :         auto await_suspend(
     304              :             std::coroutine_handle<> h,
     305              :             Ex const& ex,
     306              :             std::stop_token token) -> std::coroutine_handle<>
     307              :         {
     308           10 :             token_ = std::move(token);
     309           10 :             r_.get().reverse_resolve(h, ex, ep_, flags_, token_, &ec_, &result_);
     310           10 :             return std::noop_coroutine();
     311              :         }
     312              :     };
     313              : 
     314              : public:
     315              :     /** Destructor.
     316              : 
     317              :         Cancels any pending operations.
     318              :     */
     319              :     ~resolver();
     320              : 
     321              :     /** Construct a resolver from an execution context.
     322              : 
     323              :         @param ctx The execution context that will own 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 will own the resolver.
     332              :     */
     333              :     template<class Ex>
     334              :         requires (!std::same_as<std::remove_cvref_t<Ex>, resolver>) &&
     335              :                  capy::Executor<Ex>
     336            1 :     explicit resolver(Ex const& ex)
     337            1 :         : resolver(ex.context())
     338              :     {
     339            1 :     }
     340              : 
     341              :     /** Move constructor.
     342              : 
     343              :         Transfers ownership of the resolver resources.
     344              : 
     345              :         @param other The resolver to move from.
     346              :     */
     347            1 :     resolver(resolver&& other) noexcept
     348            1 :         : io_object(other.context())
     349              :     {
     350            1 :         impl_ = other.impl_;
     351            1 :         other.impl_ = nullptr;
     352            1 :     }
     353              : 
     354              :     /** Move assignment operator.
     355              : 
     356              :         Cancels any existing operations and transfers ownership.
     357              :         The source and destination must share the same execution context.
     358              : 
     359              :         @param other The resolver to move from.
     360              : 
     361              :         @return Reference to this resolver.
     362              : 
     363              :         @throws std::logic_error if the resolvers have different
     364              :             execution contexts.
     365              :     */
     366            2 :     resolver& operator=(resolver&& other)
     367              :     {
     368            2 :         if (this != &other)
     369              :         {
     370            2 :             if (ctx_ != other.ctx_)
     371            1 :                 detail::throw_logic_error(
     372              :                     "cannot move resolver across execution contexts");
     373            1 :             cancel();
     374            1 :             impl_ = other.impl_;
     375            1 :             other.impl_ = nullptr;
     376              :         }
     377            1 :         return *this;
     378              :     }
     379              : 
     380              :     resolver(resolver const&) = delete;
     381              :     resolver& operator=(resolver const&) = delete;
     382              : 
     383              :     /** Initiate an asynchronous resolve operation.
     384              : 
     385              :         Resolves the host and service names into a list of endpoints.
     386              : 
     387              :         @param host A string identifying a location. May be a descriptive
     388              :             name or a numeric address string.
     389              : 
     390              :         @param service A string identifying the requested service. This may
     391              :             be a descriptive name or a numeric string corresponding to a
     392              :             port number.
     393              : 
     394              :         @return An awaitable that completes with `io_result<resolver_results>`.
     395              : 
     396              :         @par Example
     397              :         @code
     398              :         auto [ec, results] = co_await r.resolve("www.example.com", "https");
     399              :         @endcode
     400              :     */
     401            5 :     auto resolve(
     402              :         std::string_view host,
     403              :         std::string_view service)
     404              :     {
     405            5 :         return resolve_awaitable(*this, host, service, resolve_flags::none);
     406              :     }
     407              : 
     408              :     /** Initiate an asynchronous resolve operation with flags.
     409              : 
     410              :         Resolves the host and service names into a list of endpoints.
     411              : 
     412              :         @param host A string identifying a location.
     413              : 
     414              :         @param service A string identifying the requested service.
     415              : 
     416              :         @param flags Flags controlling resolution behavior.
     417              : 
     418              :         @return An awaitable that completes with `io_result<resolver_results>`.
     419              :     */
     420           11 :     auto resolve(
     421              :         std::string_view host,
     422              :         std::string_view service,
     423              :         resolve_flags flags)
     424              :     {
     425           11 :         return resolve_awaitable(*this, host, service, flags);
     426              :     }
     427              : 
     428              :     /** Initiate an asynchronous reverse resolve operation.
     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              :         @return An awaitable that completes with
     436              :             `io_result<reverse_resolver_result>`.
     437              : 
     438              :         @par Example
     439              :         @code
     440              :         endpoint ep(ipv4_address({127, 0, 0, 1}), 80);
     441              :         auto [ec, result] = co_await r.resolve(ep);
     442              :         if (!ec)
     443              :             std::cout << result.host_name() << ":" << result.service_name();
     444              :         @endcode
     445              :     */
     446            3 :     auto resolve(endpoint const& ep)
     447              :     {
     448            3 :         return reverse_resolve_awaitable(*this, ep, reverse_flags::none);
     449              :     }
     450              : 
     451              :     /** Initiate an asynchronous reverse resolve operation with flags.
     452              : 
     453              :         Resolves an endpoint into a hostname and service name using
     454              :         reverse DNS lookup (PTR record query).
     455              : 
     456              :         @param ep The endpoint to resolve.
     457              : 
     458              :         @param flags Flags controlling resolution behavior. See reverse_flags.
     459              : 
     460              :         @return An awaitable that completes with
     461              :             `io_result<reverse_resolver_result>`.
     462              :     */
     463            7 :     auto resolve(endpoint const& ep, reverse_flags flags)
     464              :     {
     465            7 :         return reverse_resolve_awaitable(*this, ep, flags);
     466              :     }
     467              : 
     468              :     /** Cancel any pending asynchronous operations.
     469              : 
     470              :         All outstanding operations complete with `errc::operation_canceled`.
     471              :         Check `ec == cond::canceled` for portable comparison.
     472              :     */
     473              :     void cancel();
     474              : 
     475              : public:
     476              :     struct resolver_impl : io_object_impl
     477              :     {
     478              :         virtual void resolve(
     479              :             std::coroutine_handle<>,
     480              :             capy::executor_ref,
     481              :             std::string_view host,
     482              :             std::string_view service,
     483              :             resolve_flags flags,
     484              :             std::stop_token,
     485              :             std::error_code*,
     486              :             resolver_results*) = 0;
     487              : 
     488              :         virtual void reverse_resolve(
     489              :             std::coroutine_handle<>,
     490              :             capy::executor_ref,
     491              :             endpoint const& ep,
     492              :             reverse_flags flags,
     493              :             std::stop_token,
     494              :             std::error_code*,
     495              :             reverse_resolver_result*) = 0;
     496              : 
     497              :         virtual void cancel() noexcept = 0;
     498              :     };
     499              : 
     500              : private:
     501           31 :     inline resolver_impl& get() const noexcept
     502              :     {
     503           31 :         return *static_cast<resolver_impl*>(impl_);
     504              :     }
     505              : };
     506              : 
     507              : } // namespace boost::corosio
     508              : 
     509              : #endif
        

Generated by: LCOV version 2.3