SNTP time class
Written by Harry Fairhead   
Wednesday, 10 March 2010
Article Index
SNTP time class
Time commands
The SNTP Class
Getting the Time

 

The SNTP class

After this long theoretical description about time it is time to start coding. Start a new Windows Forms or WFP project and add a class called SNTPTIME. Change the name of the single class to SNTP.

The first thing we need is a public property to specify the URL of the SNTP time server to be used:

public string TimeServer { get; set; }

and we might as well create a UdpClient object ready to use:

private UdpClient UDP = new UdpClient();

The only method needed by the user of the class  is SNTPRequest, which sends a request for a time update to the nominated server and then waits for a response. To do this we need to construct an SNTP packet. We could do this using nothing but a raw byte array buffer but it is more useful to build a decoded set of values as part of a class or a struct. This might as well be a nested class although this isn't necessary as no use is made of the parent class from the nested class.

The set of properties we need follow the specification of the SNTP packet but using appropriate data types:

public class SNTPpacket
{
public byte LI{ get; set; }
public byte VN { get; set; }
public byte Mode { get; set; }
public byte Stratum { get; set; }
public byte Poll { get; set; }
public byte Precision { get; set; }
public Int32 RootDelay { get; set; }
public Int32
RootDispersion { get; set; }
public string RefId { get; set; }
public DateTime
RefTimeStamp { get; set; }
public DateTime
OriginateTimeStamp { get; set; }
public DateTime
ReceiveTimeStamp { get; set; }
public DateTime
TransmitTimeStamp { get; set; }
public byte[] buffer { get; set; }

Notice that some of the data types selected are still more primitive than they need be. For example Mode could be an enumeration rather than just a numeric byte value. In particular Poll and Precision haven't been converted into signed values let along powers of 2 and the RootDelay and RootDispersion have been left as integers even though there is an implied binary point after the 15th bit. You can deal with these issues if you need to make use of the values.

Also notice that there is a buffer, a simple byte array, as part of the data packet. The idea is that this is used to store the raw SNTP packet for transmission or reception and the class provides methods which pack and unpack the buffer to the individual properties. To make following the ideas easier these methods will be introduced to the SNTPPacket class as they are needed by the rest of the program. For the moment all we need is a constructor which sets the buffer to 48 bytes:


 public SNTPpacket()
{
buffer = new byte[48];
}

Sending the Request

The first thing that the SNTPRequest method has to do is construct a request packet and send it. This is fairly easy as we can simply set VN to 1 (version 1) and Mode to 3 to indicate that this is a client request:

public SNTPpacket SNTPRequest()
{
SNTPpacket request = new SNTPpacket();
request.VN=1;
request.Mode=3;

The next task is slightly more complicated in that you have to set the RefTimeStamp to the current time in UT:

request.RefTimeStamp=DateTime.UtcNow;

Now we simply call the data packet's ConvertToBuffer to convert all of the data we have entered into the byte array:

request.ConvertToBuffer();

of course we haven't written the ConvertToBuffer method yet and this has to be added to the SNTPpacket class. The first task is to transfer the data from the command word  and this is a matter of using some simple bit manipulation to construct the first byte:

public void ConvertToBuffer()
{
buffer[0] = (byte)(((LI & 0x3)<<6)|
((VN & 0x3)<<3)|Mode & 0x07);
buffer[1] = Stratum;
buffer[2] = Poll;
buffer[3] = Precision;

The first byte is the most difficult. The LI is first "anded" with a mask equal to 11 in binary which extracts the lower two bits. These are then shifted to the top end of the byte. The process is repeated with the other two bit fields that have to be packed into the first byte.

The only other field we have to transfer to the buffer is the RefTimeStamp but it is worth doing this in a general way so that it is easy to extend the code to an SNTP server which has to load more of the time stamps. First we have to convert the DateTime format time into an SNTP time value:

Int64 SNTPTime=
ConvertToSNTPTime(RefTimeStamp);

Although this is stored in a 64 bit integer only the bottom 32 bits are non-zero. The ConvertToSNTPTime method is:

private Int64 ConvertToSNTPTime(
DateTime T)
{
return T.Subtract(
new DateTime(1900, 1, 1)).Ticks/
TimeSpan.TicksPerSecond;
}

The basic idea is to subtract the start value of the SNTP time system, then convert to ticks and finally convert to seconds by dividing by TicksPerSecond.

Finally all we have to do is load the low four bytes in the Int64 into the buffer in the correct location:

LoadSNTPTime(SNTPTime,24);

again we have assumed that there is a suitable method to do the job - but we now have to write it! This would be easy but the SNTP byte order is the reverse of the natural byte order used by .NET and Intel architecture in general. This means we have to use an explicit loop to move the bytes:

private void LoadSNTPTime(Int64 T,
int start)
{
 byte[] temp = BitConverter.GetBytes(T);
 for (int i = 0; i < 4; i++)
 {
buffer[start+3 - i] = temp[i];
}
}

The Bitconverter class is used to convert the Int64 into 8 bytes of a byte array and then a for loop transfers these in reverse order into the buffer.

This completes the loading of the buffer and now we can continue with sending the UDP datagram to the time server. In more elaborate situations you may need to transfer more data from the properites to the buffer.

To send the datagram we first connect to the server. As UDP is a connection-less  protocol this might seem a bit strange but what it does is to initialise the UdpClient with an IP address and port:

UDP.Connect(TimeServer, 123);

Now we can send the datagram:

int count = UDP.Send(request.buffer, 
request.buffer.Length);

you can check count to make sure that all 48 bytes were sent. This is a blocking call and nothing happens until the datagram has been sent. In a more sophisticated application you would need to use the asynchronous call to BeginSend and use a Try Catch block to trap exceptions - but in this demonstration this is ignored for simplicity.

<ASIN:1584884657>

<ASIN:0596100329>

<ASIN:0768682320>

<ASIN:0124660517>



Last Updated ( Sunday, 14 March 2010 )
 
 

   
RSS feed of all content
I Programmer - full contents
Copyright © 2014 i-programmer.info. All Rights Reserved.
Joomla! is Free Software released under the GNU/GPL License.