The SNTP Client

Looking at what a SNTP client looks like.

The SNTP_Helper class bundles all the operations required to assemble a time petition, send that petition to a time server, listen for the reply and then extract the time information. Several other classes are required manipulate the SNTP information.

A SNTP Client

The windows dialog box is a standard form that links a SNTP petition press of the button to a call a SNTP_Helper class. It is this class where all the work is done to obtain a timestamp. This is the button call.

        SNTP_Helper sntp = new SNTP_Helper();
        private void OnSNTP_Petition(object sender, MouseEventArgs e)
        {
            ErrorText.Text = ";
            //  Set up with latest IP information. If pool we need the IP
            sntp.NTP_IPADDRESS = sntp_address.ToString();
            //  Look up check box for version
            if (VersionCheckBox.Checked) bSNTPv4 = true;
            else bSNTPv4 = false;
            if (AuthorisationBox.Checked) bAuthorisation = true;
            else bAuthorisation = false;
            if (sntp.GetTime(ref initial_timestamp, bSNTPv4, bAuthorisation))
            {
                //  Display the time etc..
                ErrorText.ForeColor = Color.Black;
                ErrorText.Text = "Time retrieved correctly";
                //  Divide timestamp by 429 to get ticks, then a TimeSpan for the secs
                TimeSpan delay = TimeSpan.FromTicks(((Int32)(((Double)sntp.DELTA) / ((Double)429))));
            TimeSpan offset = TimeSpan.FromTicks(((Int32)(((Double)sntp.THETA) / ((Double)429))));
            try
            {
                DelayBox.Text = Convert.ToUInt16(delay.TotalMilliseconds).ToString();
            }
            catch { DelayBox.Text = "0"; }
            try
            {
                OffsetBox.Text = Convert.ToUInt16(offset.TotalMilliseconds).ToString();
            }
            catch { OffsetBox.Text = "0"; }
            UTCBox.Text = sntp.TIMESTAMP.ToString();
            EpochBox.Text = SNTP_Helper.UTCToEpoch(sntp.TIMESTAMP).ToString();
            GMTBox.Text = sntp.TIME.ToString("yyyy/MM/dd    HH:mm:ss    tt");
            //  Root Delay is a 32 short formatU
            RootDelayBox.Text = SNTP_Helper.ShortFormatToMSec(sntp.ROOTDELAY).ToString();
            RootDispersionBox.Text = SNTP_Helper.ShortFormatToMSec(sntp.ROOTDISPERSION).ToString();
            TimeZone zone = TimeZone.CurrentTimeZone;
            DateTime local_time = zone.ToLocalTime(sntp.TIME);
            LocalBox.Text = local_time.ToString("yyyy/MM/dd    HH:mm:ss    tt");
            TimeSpan time_offset = local_time - sntp.TIME;
            GMTOffsetBox.Text = time_offset.TotalHours.ToString();
            StratumBox.Text = sntp.STRATUM.ToString();
            VersionBox.Text = sntp.VERSION.ToString();
            //  precison -18 = 1 microsec
            sbyte precision = (sbyte)sntp.PRECISION;
            Int32 precision_mSec = (Int32)(Math.Exp(precision) * 1000);
            AccuracyBox.Text = precision_mSec.ToString();
        }
        else
        {
            //  Error message 
            ErrorText.ForeColor = Color.Red;
            ErrorText.Text = sntp.LastErrorString;
            //  Empty all box's
            UTCBox.Text = " ";
            EpochBox.Text = " ";
            GMTBox.Text = " ";
            LocalBox.Text = " ";
            GMTOffsetBox.Text = " ";
            DelayBox.Text = " ";
            OffsetBox.Text = " ";
            RootDelayBox.Text = " ";
            RootDispersionBox.Text = " ";
            StratumBox.Text = " ";
            VersionBox.Text = " ";
            AccuracyBox.Text = " ";
        }
    }

The text box's are "as per" the information placement on the form.

Inside SNTP_Helper.GetTime is where all the work is done. Taking the NTP version to be used and if authentiction is to be used, the correct NTP frame is generated, sent, reply awaited and reply decoded to extract the time information. This done using non-blocking functions so the form does not loose "response".. In essence there there are two action "the send" and "the receive".

    public class SNTP_Helper
    {
        private Ntp_Packet NTPdata = new Ntp_Packet();                  //  The NTP data frame
        private DateTime  tm_initial;                                   //	Initial time mark
        private DateTime tm_latest;
        //  There are 4 timestamps that are tracked and 4 kept as current
        private UInt64 t_ref_timestamp = 0;
        private UInt64 T_ref_timestamp = 0;
        private UInt64 t_orig_timestamp = 0;
        private UInt64 T_orig_timestamp = 0;
        private UInt64 t_recv_timestamp = 0;
        private UInt64 T_recv_timestamp = 0;
        private UInt64 t_tx_timestamp = 0;
        private UInt64 T_tx_timestamp = 0;
        private UInt64 t_arrival_timestamp = 0;
        private UInt64 theta = 0;
        private UInt64 delta = 0;
        private UInt32 root_delay = 0;
        private UInt32 root_dispersion = 0;
        private Byte stratum = 0;
        private Byte version = 0;
        private Byte precision = 0;

        //  The socket of SNTP usage
        Socket TimeServerSocket = new Socket(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp);
        private Int32 timeout = 100;                                            // mSec
        private IPAddress NTP_Address = IPAddress.Parse("143.210.16.201");        //  default

        private static ManualResetEvent TimeServerSocketConnectDone = new ManualResetEvent(false);
        private static Boolean bConnected = false;
        private static ManualResetEvent TimeServerSocketSendDone = new ManualResetEvent(false);
        private static Boolean bSent = false;
        private static Int32 bytesSent = 0;
        private static ManualResetEvent TimeServerSocketReceiveDone = new ManualResetEvent(false);
        private static Boolean bReceive = false;
        private static Int32 bytesReceived = 0;
        private static Byte[] response = null;

        private static Exception connect_exception = new Exception();
        private static Exception send_exception = new Exception();
        private static Exception receive_exception = new Exception();
        private static Exception finalise_exception = new Exception();

        private Int32 iLastErrorNumber = 0;
        private String[] sLastErrorString =
        {
            ",
            "Problem: Timed out connecting to the time server",
            "Problem: Exception connecting to the time server",
            "Problem: Timed out sending time petition",
            "Problem: Exception sending time petition",
            "Problem: Timed out receiving time data",
            "Problem: Exception receiving time data",
            "Problem: Exception finalising SNTP transction",
            "Problem: Duplicate reply frame",
            "Problem: Bogus reply frame SntpV4",
            "Problem: Bogus reply frame SntpV3",
            "Problem: The number of ticks is out of range",
            "Problem: Authentication returns invalid KeyId"
        };

        private Int32 connect_timeout = 25;
        private Int32 send_timeout = 50;
        private Int32 receive_timeout = 250;
        private Boolean bSntpV4 = false;
        private Boolean bAuthorisation = false;
        private UInt64 corrected_timestamp = 0;

        public SNTP_Helper()
        { }

        public Int32 TIMEOUT { get { return timeout; } set { timeout = value; } }
        public Int32 CONNECT_TIMEOUT { get { return connect_timeout; } set { connect_timeout = value; } }
        public Int32 SEND_TIMEOUT { get { return send_timeout; } set { send_timeout = value; } }
        public Int32 RECEIVE_TIMEOUT { get { return receive_timeout; } set { receive_timeout = value; } }
        public Exception GET_CONNECT_EXCEPTION { get { return connect_exception; } }
        public Exception GET_SEND_EXCEPTION { get { return send_exception; } }
        public Exception GET_RECEIVE_EXCEPTION { get { return receive_exception; } }
        public Exception GET_FINALISE_EXCEPTION { get { return finalise_exception; } }
        public UInt64 THETA { get { return theta; } }
        public UInt64 DELTA { get { return delta; } }
        public DateTime TIME { get { return tm_latest; } }
        public UInt64 TIMESTAMP { get { return corrected_timestamp; } }
        public UInt32 ROOTDELAY { get { return root_delay; } }
        public UInt32 ROOTDISPERSION { get { return root_dispersion; } }
        public Byte STRATUM { get { return stratum; } }
        public Byte VERSION { get { return version; } }
        public Byte PRECISION { get { return precision; } }

First there is the entry to the SNTP_Helper class. Here the timestamps to be held are defined, the default values defined, space set aside for the UDP socket that is to be used for communication. there are events and handlers set aside for controlling the usage of the socket. Exceptions are handled and error messages generated for diagnosis of communication failures. Finally the communication timeouts are given default values.

There are a number of methods for information exchange with the internals of the class.