Xclox
C++11 header-only cross-platform date and time library with an asynchronous NTP client
query_series.hpp
1 /*
2  * Copyright (c) 2024 Abdullatif Kalla.
3  *
4  * This source code is licensed under the MIT license found in the
5  * LICENSE.txt file in the root directory of this source tree.
6  */
7 
8 #ifndef XCLOX_COMPOUND_QUERY_HPP
9 #define XCLOX_COMPOUND_QUERY_HPP
10 
11 #include "query_single.hpp"
12 
13 namespace xclox {
14 
15 namespace ntp {
16 
24  class QuerySeries {
25  public:
32  using DefaultTimeout = internal::DefaultTimeout<QuerySingle, 5000>;
33 
35 
37  explicit QuerySeries(asio::io_context& io, const asio::ip::udp::resolver::results_type& endpoints, const std::chrono::milliseconds& timeout)
38  : m_io(io)
39  , m_endpoints(endpoints)
40  , m_timer(io, timeout)
41  {
42  }
43 
52  static std::weak_ptr<QuerySeries> start(asio::io_context& io, const asio::ip::udp::resolver::results_type& endpoints, Callback callback, const std::chrono::milliseconds& timeout = std::chrono::milliseconds(DefaultTimeout::ms))
53  {
54  if (!callback || endpoints.empty()) {
55  return {};
56  }
57  auto query = std::make_shared<QuerySeries>(io, endpoints, timeout);
58  query->m_timer.async_wait([query](const asio::error_code& error) {
59  if (error != asio::error::operation_aborted) {
60  query->m_timer.expires_at(std::chrono::steady_clock::time_point::min());
61  if (auto currentQuery = query->m_subquery.lock())
62  currentQuery->cancel();
63  }
64  });
65  query->m_callback = [query, callback](const asio::ip::udp::endpoint& endpoint, const asio::error_code& error, const Packet& packet, const std::chrono::steady_clock::duration& rtt) {
66  if (error && error != asio::error::operation_aborted && query->index(endpoint) < query->m_endpoints.size() - 1) {
67  query->m_subquery = QuerySingle::start(query->m_io, std::next(query->m_endpoints.cbegin(), query->index(endpoint) + 1)->endpoint(), query->m_callback);
68  } else {
69  query->m_timer.cancel();
70  query->m_callback = {};
71  callback(
72  endpoint,
73  query->m_timer.expiry() == std::chrono::steady_clock::time_point::max() ? asio::error::operation_aborted : (query->m_timer.expiry() == std::chrono::steady_clock::time_point::min() ? asio::error::timed_out : error),
74  packet,
75  rtt);
76  }
77  };
78  query->m_subquery = QuerySingle::start(io, endpoints.cbegin()->endpoint(), query->m_callback);
79  return query;
80  }
81 
83  void cancel()
84  {
85  m_timer.expires_at(std::chrono::steady_clock::time_point::max());
86  if (auto query = m_subquery.lock())
87  query->cancel();
88  }
89 
90  private:
91  size_t index(const asio::ip::udp::endpoint& endpoint) const
92  {
93  return std::distance(m_endpoints.cbegin(), std::find_if(m_endpoints.cbegin(), m_endpoints.cend(), [&endpoint](const asio::ip::basic_resolver_entry<asio::ip::udp>& entry) { return entry.endpoint() == endpoint; }));
94  }
95 
96  asio::io_context& m_io;
97  asio::ip::udp::resolver::results_type m_endpoints;
98  asio::steady_timer m_timer;
99  Callback m_callback;
100  std::weak_ptr<QuerySingle> m_subquery;
101  };
102 
103 } // namespace ntp
104 
105 } // namespace xclox
106 
107 #endif // XCLOX_COMPOUND_QUERY_HPP
Packet is an immutable raw NTP packet.
Definition: packet.hpp:54
QuerySeries is an ephemeral class representing a series of NTP queries.
Definition: query_series.hpp:24
static std::weak_ptr< QuerySeries > start(asio::io_context &io, const asio::ip::udp::resolver::results_type &endpoints, Callback callback, const std::chrono::milliseconds &timeout=std::chrono::milliseconds(DefaultTimeout::ms))
Starts querying the given endpoints one at a time until success or all endpoints are queried.
Definition: query_series.hpp:52
QuerySingle::Callback Callback
Type of query callback.
Definition: query_series.hpp:31
QuerySeries(asio::io_context &io, const asio::ip::udp::resolver::results_type &endpoints, const std::chrono::milliseconds &timeout)
Constructs a NTP query series on the given context that targets the given endpoints one at a time unt...
Definition: query_series.hpp:37
void cancel()
Cancels the query reporting asio::error::operation_aborted to the caller.
Definition: query_series.hpp:83
internal::DefaultTimeout< QuerySingle, 5000 > DefaultTimeout
Type of query timeout milliseconds holder.
Definition: query_series.hpp:32
static std::weak_ptr< QuerySingle > start(asio::io_context &io, const asio::ip::udp::endpoint &server, Callback callback, const std::chrono::milliseconds &timeout=std::chrono::milliseconds(DefaultTimeout::ms))
Starts querying the given endpoint.
Definition: query_single.hpp:68
std::function< void(const asio::ip::udp::endpoint &, const asio::error_code &, const Packet &, const std::chrono::steady_clock::duration &)> Callback
Type of query callback.
Definition: query_single.hpp:48