using System; using System.Net; using System.Net.Sockets; #if NETSTANDARD || NETCOREAPP using System.Threading.Tasks; #endif namespace LiteNetLib.Utils { /// /// Make NTP request. /// /// 1. Create the object by method. /// /// /// 2. Use method to send requests. /// /// /// 3. Call to release the socket AFTER you have received the response or some timeout. /// If you close the socket too early, you may miss the response. /// /// public sealed class NtpRequest : INetSocketListener { public const int DefaultPort = 123; private readonly NetSocket _socket; private readonly Action _onRequestComplete; private readonly IPEndPoint _ntpEndPoint; /// /// Initialize object, open socket. /// /// NTP Server endpoint /// callback (called from other thread!) private NtpRequest(IPEndPoint endPoint, Action onRequestComplete) { _ntpEndPoint = endPoint; _onRequestComplete = onRequestComplete; // Create and start socket _socket = new NetSocket(this); _socket.Bind( IPAddress.Any, IPAddress.IPv6Any, 0, false, endPoint.AddressFamily == AddressFamily.InterNetworkV6 ? IPv6Mode.SeparateSocket : IPv6Mode.Disabled); } /// /// Create the requests for NTP server, open socket. /// /// NTP Server address. /// callback (called from other thread!) public static NtpRequest Create(IPEndPoint endPoint, Action onRequestComplete) { return new NtpRequest(endPoint, onRequestComplete); } /// /// Create the requests for NTP server (default port), open socket. /// /// NTP Server address. /// callback (called from other thread!) public static NtpRequest Create(IPAddress ipAddress, Action onRequestComplete) { IPEndPoint endPoint = new IPEndPoint(ipAddress, DefaultPort); return Create(endPoint, onRequestComplete); } /// /// Create the requests for NTP server, open socket. /// /// NTP Server address. /// port /// callback (called from other thread!) public static NtpRequest Create(string ntpServerAddress, int port, Action onRequestComplete) { IPEndPoint endPoint = NetUtils.MakeEndPoint(ntpServerAddress, port); return Create(endPoint, onRequestComplete); } /// /// Create the requests for NTP server (default port), open socket. /// /// NTP Server address. /// callback (called from other thread!) public static NtpRequest Create(string ntpServerAddress, Action onRequestComplete) { IPEndPoint endPoint = NetUtils.MakeEndPoint(ntpServerAddress, DefaultPort); return Create(endPoint, onRequestComplete); } #if NETSTANDARD || NETCOREAPP /// /// Requests asynchronously NTP server for time offset /// /// NTP Server address. /// Scheduled task public static async Task RequestAsync(string ntpServerAddress) { var t = new TaskCompletionSource(); await Task.Run(() => { NtpRequest request = null; request = Create(ntpServerAddress, (ntpPacket) => { request.Close(); t.SetResult(ntpPacket); }); request.Send(); }); return await t.Task; } #endif /// /// Send request to the NTP server calls callback (if success). /// In case of error the callback is called with null param. /// public void Send() { SocketError errorCode = 0; var packet = new NtpPacket(); packet.ValidateRequest(); // not necessary byte[] sendData = packet.Bytes; var sendCount = _socket.SendTo(sendData, 0, sendData.Length, _ntpEndPoint, ref errorCode); if (errorCode != 0 || sendCount != sendData.Length) { _onRequestComplete(null); } } /// /// Close socket. /// public void Close() { _socket.Close(false); } /// /// Handle received data: transform bytes to NtpPacket, close socket and call the callback. /// void INetSocketListener.OnMessageReceived(byte[] data, int length, SocketError errorCode, IPEndPoint remoteEndPoint) { DateTime destinationTimestamp = DateTime.UtcNow; if (!remoteEndPoint.Equals(_ntpEndPoint)) return; if (length < 48) { NetDebug.Write(NetLogLevel.Trace, "NTP response too short: {}", length); _onRequestComplete(null); return; } NtpPacket packet = NtpPacket.FromServerResponse(data, destinationTimestamp); try { packet.ValidateReply(); } catch (InvalidOperationException ex) { NetDebug.Write(NetLogLevel.Trace, "NTP response error: {}", ex.Message); packet = null; } _onRequestComplete(packet); } } }