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);
}
}
}