Xclox
C++11 header-only cross-platform date and time library with an asynchronous NTP client
query_single.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_SIMPLE_QUERY_HPP
9 #define XCLOX_SIMPLE_QUERY_HPP
10 
11 #include "packet.hpp"
12 
13 #define ASIO_NO_DEPRECATED
14 #include <asio.hpp>
15 
16 #include <memory>
17 
18 namespace xclox {
19 
20 namespace ntp {
21 
22  namespace internal {
23 
25  template <typename T, int N>
26  struct DefaultTimeout {
27  static const int ms = N;
28  };
29  template <typename T, int N>
30  const int DefaultTimeout<T, N>::ms;
32  }
33 
41  class QuerySingle {
42  public:
48  using Callback = std::function<void(const asio::ip::udp::endpoint&, const asio::error_code&, const Packet&, const std::chrono::steady_clock::duration&)>;
49  using DefaultTimeout = internal::DefaultTimeout<QuerySingle, 3000>;
50 
52 
54  explicit QuerySingle(asio::io_context& io, const std::chrono::milliseconds& timeout)
55  : m_timer(io, timeout)
56  , m_socket(io, asio::ip::udp::v4())
57  {
58  }
59 
68  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))
69  {
70  if (!callback) {
71  return {};
72  }
73  auto query = std::make_shared<QuerySingle>(io, timeout);
74  query->m_timer.async_wait([query](const asio::error_code& error) {
75  if (error != asio::error::operation_aborted) {
76  query->m_timer.expires_at(std::chrono::steady_clock::time_point::min());
77  query->close();
78  }
79  });
80  Packet packet(0, 4, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, Timestamp(std::chrono::system_clock::now()).value());
81  const auto& time = std::chrono::steady_clock::now();
82  query->m_socket.async_send_to(
83  asio::buffer(packet.data()),
84  server,
85  [query, server, callback, packet, time](const asio::error_code& error, std::size_t) {
86  if (error) {
87  query->m_timer.cancel();
88  callback(server, error, packet, std::chrono::steady_clock::now() - time);
89  return;
90  }
91  query->m_socket.async_receive_from(
92  asio::buffer(query->m_buffer),
93  query->m_endpoint,
94  [query, server, callback, time](const asio::error_code& error, std::size_t size) {
95  query->m_timer.cancel();
96  if (error || size != query->m_buffer.size() || query->m_timer.expiry() == std::chrono::steady_clock::time_point::max()) {
97  callback(server,
98  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 ? error : asio::error::message_size)),
99  Packet(),
100  std::chrono::steady_clock::now() - time);
101  } else {
102  callback(server, error, Packet(query->m_buffer), std::chrono::steady_clock::now() - time);
103  }
104  });
105  });
106  return query;
107  }
108 
110  void cancel()
111  {
112  m_timer.expires_at(std::chrono::steady_clock::time_point::max());
113  close();
114  }
115 
116  private:
117  void close()
118  {
119  if (m_socket.is_open()) {
120  asio::error_code ec;
121  m_socket.close(ec);
122  }
123  }
124 
125  asio::steady_timer m_timer;
126  asio::ip::udp::socket m_socket;
127  asio::ip::udp::endpoint m_endpoint;
128  Packet::DataType m_buffer;
129  };
130 
131 } // namespace ntp
132 
133 } // namespace xclox
134 
135 #endif // XCLOX_SIMPLE_QUERY_HPP
Packet is an immutable raw NTP packet.
Definition: packet.hpp:54
DataType data() const
Returns a raw data representation of the underlying packet.
Definition: packet.hpp:113
QuerySingle is an ephemeral class representing a single NTP query.
Definition: query_single.hpp:41
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
QuerySingle(asio::io_context &io, const std::chrono::milliseconds &timeout)
Constructs a single NTP query on the given context that runs within the given timeout duration.
Definition: query_single.hpp:54
internal::DefaultTimeout< QuerySingle, 3000 > DefaultTimeout
Type of query timeout milliseconds holder.
Definition: query_single.hpp:49
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
void cancel()
Cancels the query reporting asio::error::operation_aborted to the caller.
Definition: query_single.hpp:110
Timestamp is an immutable class representing a NTP timestamp.
Definition: timestamp.hpp:45