|SNTP Time Class|
|Written by Harry Fairhead|
|Tuesday, 23 December 2014|
Page 2 of 3
The time stamps
When you get the time packet back it contains four time stamps – why, when you just want one time i.e. now!
The reason is that the time that you get back in Transmit Time Stamp (TTS) was the time at the server when the packet left the server. It takes time for the packet to reach you and this has to be taken into account.
The Originate Time Stamp (OTS), i.e. the time you sent the request packet and the Receive Time Stamp (RTS), i.e. the time the server received the packet, and time the packet actually arrived can be used to estimate the round trip time and correct the final time to the last few tenths of a millisecond.
In practice you can ignore all of this unless you really are trying for high accuracy. All that matters is the TTS which contains the time as measured at the server when the packet was transmitted – this will be accurate to a few milliseconds unless the conditions on the Internet are so bad that you would be well advised to use an alternative server.
So it looks as if we can ignore all the time stamps in the requesting package and only look at the TTS value in the return package.
The only complication is that in practice servers seem to ignore packets with unreasonable values of OTS in the requesting packet so we have to set this even if we are not going to make use of it.
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:
and we might as well create a UdpClient object ready to use:
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:
Notice that some of the data types selected are 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 alone 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:
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:
The next task is slightly more complicated in that you have to set the RefTimeStamp to the current time in UT:
Now we simply call the data packet's ConvertToBuffer to convert all of the data we have entered into the byte array:
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:
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:
Although this is stored in a 64 bit integer only the bottom 32 bits are non-zero. The ConvertToSNTPTime method is:
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:
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:
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:
Now we can send the datagram:
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.
|Last Updated ( Sunday, 16 September 2018 )|