using System;
namespace LiteNetLib.Utils
{
///
/// Represents RFC4330 SNTP packet used for communication to and from a network time server.
///
///
///
/// Most applications should just use the property.
///
///
/// The same data structure represents both request and reply packets.
/// Request and reply differ in which properties are set and to what values.
///
///
/// The only real property is .
/// All other properties read from and write to the underlying byte array
/// with the exception of ,
/// which is not part of the packet on network and it is instead set locally after receiving the packet.
///
///
/// Copied from GuerrillaNtp project
/// with permission from Robert Vazan (@robertvazan) under MIT license, see https://github.com/RevenantX/LiteNetLib/pull/236
///
///
public class NtpPacket
{
private static readonly DateTime Epoch = new DateTime(1900, 1, 1);
///
/// Gets RFC4330-encoded SNTP packet.
///
///
/// Byte array containing RFC4330-encoded SNTP packet. It is at least 48 bytes long.
///
///
/// This is the only real property. All other properties except
/// read from or write to this byte array.
///
public byte[] Bytes { get; private set; }
///
/// Gets the leap second indicator.
///
///
/// Leap second warning, if any. Special value
/// indicates unsynchronized server clock.
/// Default is .
///
///
/// Only servers fill in this property. Clients can consult this property for possible leap second warning.
///
public NtpLeapIndicator LeapIndicator
{
get { return (NtpLeapIndicator)((Bytes[0] & 0xC0) >> 6); }
}
///
/// Gets or sets protocol version number.
///
///
/// SNTP protocol version. Default is 4, which is the latest version at the time of this writing.
///
///
/// In request packets, clients should leave this property at default value 4.
/// Servers usually reply with the same protocol version.
///
public int VersionNumber
{
get { return (Bytes[0] & 0x38) >> 3; }
private set { Bytes[0] = (byte)((Bytes[0] & ~0x38) | value << 3); }
}
///
/// Gets or sets SNTP packet mode, i.e. whether this is client or server packet.
///
///
/// SNTP packet mode. Default is in newly created packets.
/// Server reply should have this property set to .
///
public NtpMode Mode
{
get { return (NtpMode)(Bytes[0] & 0x07); }
private set { Bytes[0] = (byte)((Bytes[0] & ~0x07) | (int)value); }
}
///
/// Gets server's distance from the reference clock.
///
///
///
/// Distance from the reference clock. This property is set only in server reply packets.
/// Servers connected directly to reference clock hardware set this property to 1.
/// Statum number is incremented by 1 on every hop down the NTP server hierarchy.
///
///
/// Special value 0 indicates that this packet is a Kiss-o'-Death message
/// with kiss code stored in .
///
///
public int Stratum { get { return Bytes[1]; } }
///
/// Gets server's preferred polling interval.
///
///
/// Polling interval in log2 seconds, e.g. 4 stands for 16s and 17 means 131,072s.
///
public int Poll { get { return Bytes[2]; } }
///
/// Gets the precision of server clock.
///
///
/// Clock precision in log2 seconds, e.g. -20 for microsecond precision.
///
public int Precision { get { return (sbyte)Bytes[3]; } }
///
/// Gets the total round-trip delay from the server to the reference clock.
///
///
/// Round-trip delay to the reference clock. Normally a positive value smaller than one second.
///
public TimeSpan RootDelay { get { return GetTimeSpan32(4); } }
///
/// Gets the estimated error in time reported by the server.
///
///
/// Estimated error in time reported by the server. Normally a positive value smaller than one second.
///
public TimeSpan RootDispersion { get { return GetTimeSpan32(8); } }
///
/// Gets the ID of the time source used by the server or Kiss-o'-Death code sent by the server.
///
///
///
/// ID of server's time source or Kiss-o'-Death code.
/// Purpose of this property depends on value of property.
///
///
/// Stratum 1 servers write here one of several special values that describe the kind of hardware clock they use.
///
///
/// Stratum 2 and lower servers set this property to IPv4 address of their upstream server.
/// If upstream server has IPv6 address, the address is hashed, because it doesn't fit in this property.
///
///
/// When server sets to special value 0,
/// this property contains so called kiss code that instructs the client to stop querying the server.
///
///
public uint ReferenceId { get { return GetUInt32BE(12); } }
///
/// Gets or sets the time when the server clock was last set or corrected.
///
///
/// Time when the server clock was last set or corrected or null when not specified.
///
///
/// This Property is usually set only by servers. It usually lags server's current time by several minutes,
/// so don't use this property for time synchronization.
///
public DateTime? ReferenceTimestamp { get { return GetDateTime64(16); } }
///
/// Gets or sets the time when the client sent its request.
///
///
/// This property is null in request packets.
/// In reply packets, it is the time when the client sent its request.
/// Servers copy this value from
/// that they find in received request packet.
///
///
///
public DateTime? OriginTimestamp { get { return GetDateTime64(24); } }
///
/// Gets or sets the time when the request was received by the server.
///
///
/// This property is null in request packets.
/// In reply packets, it is the time when the server received client request.
///
///
///
public DateTime? ReceiveTimestamp { get { return GetDateTime64(32); } }
///
/// Gets or sets the time when the packet was sent.
///
///
/// Time when the packet was sent. It should never be null.
/// Default value is .
///
///
/// This property must be set by both clients and servers.
///
///
///
public DateTime? TransmitTimestamp { get { return GetDateTime64(40); } private set { SetDateTime64(40, value); } }
///
/// Gets or sets the time of reception of response SNTP packet on the client.
///
///
/// Time of reception of response SNTP packet on the client. It is null in request packets.
///
///
/// This property is not part of the protocol and has to be set when reply packet is received.
///
///
///
public DateTime? DestinationTimestamp { get; private set; }
///
/// Gets the round-trip time to the server.
///
///
/// Time the request spent traveling to the server plus the time the reply spent traveling back.
/// This is calculated from timestamps in the packet as (t1 - t0) + (t3 - t2)
/// where t0 is ,
/// t1 is ,
/// t2 is ,
/// and t3 is .
/// This property throws an exception in request packets.
///
public TimeSpan RoundTripTime
{
get
{
CheckTimestamps();
return (ReceiveTimestamp.Value - OriginTimestamp.Value) + (DestinationTimestamp.Value - TransmitTimestamp.Value);
}
}
///
/// Gets the offset that should be added to local time to synchronize it with server time.
///
///
/// Time difference between server and client. It should be added to local time to get server time.
/// It is calculated from timestamps in the packet as 0.5 * ((t1 - t0) - (t3 - t2))
/// where t0 is ,
/// t1 is ,
/// t2 is ,
/// and t3 is .
/// This property throws an exception in request packets.
///
public TimeSpan CorrectionOffset
{
get
{
CheckTimestamps();
return TimeSpan.FromTicks(((ReceiveTimestamp.Value - OriginTimestamp.Value) - (DestinationTimestamp.Value - TransmitTimestamp.Value)).Ticks / 2);
}
}
///
/// Initializes default request packet.
///
///
/// Properties and
/// are set appropriately for request packet. Property
/// is set to .
///
public NtpPacket() : this(new byte[48])
{
Mode = NtpMode.Client;
VersionNumber = 4;
TransmitTimestamp = DateTime.UtcNow;
}
///
/// Initializes packet from received data.
///
internal NtpPacket(byte[] bytes)
{
if (bytes.Length < 48)
throw new ArgumentException("SNTP reply packet must be at least 48 bytes long.", "bytes");
Bytes = bytes;
}
///
/// Initializes packet from data received from a server.
///
/// Data received from the server.
/// Utc time of reception of response SNTP packet on the client.
///
public static NtpPacket FromServerResponse(byte[] bytes, DateTime destinationTimestamp)
{
return new NtpPacket(bytes) { DestinationTimestamp = destinationTimestamp };
}
internal void ValidateRequest()
{
if (Mode != NtpMode.Client)
throw new InvalidOperationException("This is not a request SNTP packet.");
if (VersionNumber == 0)
throw new InvalidOperationException("Protocol version of the request is not specified.");
if (TransmitTimestamp == null)
throw new InvalidOperationException("TransmitTimestamp must be set in request packet.");
}
internal void ValidateReply()
{
if (Mode != NtpMode.Server)
throw new InvalidOperationException("This is not a reply SNTP packet.");
if (VersionNumber == 0)
throw new InvalidOperationException("Protocol version of the reply is not specified.");
if (Stratum == 0)
throw new InvalidOperationException(string.Format("Received Kiss-o'-Death SNTP packet with code 0x{0:x}.", ReferenceId));
if (LeapIndicator == NtpLeapIndicator.AlarmCondition)
throw new InvalidOperationException("SNTP server has unsynchronized clock.");
CheckTimestamps();
}
private void CheckTimestamps()
{
if (OriginTimestamp == null)
throw new InvalidOperationException("Origin timestamp is missing.");
if (ReceiveTimestamp == null)
throw new InvalidOperationException("Receive timestamp is missing.");
if (TransmitTimestamp == null)
throw new InvalidOperationException("Transmit timestamp is missing.");
if (DestinationTimestamp == null)
throw new InvalidOperationException("Destination timestamp is missing.");
}
private DateTime? GetDateTime64(int offset)
{
var field = GetUInt64BE(offset);
if (field == 0)
return null;
return new DateTime(Epoch.Ticks + Convert.ToInt64(field * (1.0 / (1L << 32) * 10000000.0)));
}
private void SetDateTime64(int offset, DateTime? value)
{
SetUInt64BE(offset, value == null ? 0 : Convert.ToUInt64((value.Value.Ticks - Epoch.Ticks) * (0.0000001 * (1L << 32))));
}
private TimeSpan GetTimeSpan32(int offset)
{
return TimeSpan.FromSeconds(GetInt32BE(offset) / (double)(1 << 16));
}
private ulong GetUInt64BE(int offset)
{
return SwapEndianness(BitConverter.ToUInt64(Bytes, offset));
}
private void SetUInt64BE(int offset, ulong value)
{
FastBitConverter.GetBytes(Bytes, offset, SwapEndianness(value));
}
private int GetInt32BE(int offset)
{
return (int)GetUInt32BE(offset);
}
private uint GetUInt32BE(int offset)
{
return SwapEndianness(BitConverter.ToUInt32(Bytes, offset));
}
private static uint SwapEndianness(uint x)
{
return ((x & 0xff) << 24) | ((x & 0xff00) << 8) | ((x & 0xff0000) >> 8) | ((x & 0xff000000) >> 24);
}
private static ulong SwapEndianness(ulong x)
{
return ((ulong)SwapEndianness((uint)x) << 32) | SwapEndianness((uint)(x >> 32));
}
}
///
/// Represents leap second warning from the server that instructs the client to add or remove leap second.
///
///
public enum NtpLeapIndicator
{
///
/// No leap second warning. No action required.
///
NoWarning,
///
/// Warns the client that the last minute of the current day has 61 seconds.
///
LastMinuteHas61Seconds,
///
/// Warns the client that the last minute of the current day has 59 seconds.
///
LastMinuteHas59Seconds,
///
/// Special value indicating that the server clock is unsynchronized and the returned time is unreliable.
///
AlarmCondition
}
///
/// Describes SNTP packet mode, i.e. client or server.
///
///
public enum NtpMode
{
///
/// Identifies client-to-server SNTP packet.
///
Client = 3,
///
/// Identifies server-to-client SNTP packet.
///
Server = 4,
}
}