'*
'* A C# SNTP Client
'* 
'* Copyright (C)2001-2003 Valer BOCAN <vbocan@dataman.ro>
'* All Rights Reserved
'* 
'* VB.NET port by Ray Frankulin <random0000@cox.net>
'*
'* You may download the latest version from http://www.dataman.ro/sntp
'* If you find this class useful and would like to support my existence, please have a
'* look at my Amazon wish list at
'* http://www.amazon.com/exec/obidos/wishlist/ref=pd_wt_3/103-6370142-9973408
'* or make a donation to my Delta Forth .NET project, at
'* http://shareit1.element5.com/product.html?productid=159082&languageid=1&stylefrom=159082&backlink=http%3A%2F%2Fwww.dataman.ro&currencies=USD
'* 
'* Last modified: September 20, 2003
'*  
'* Permission is hereby granted, free of charge, to any person obtaining a
'* copy of this software and associated documentation files (the
'* "Software"), to deal in the Software without restriction, including
'* without limitation the rights to use, copy, modify, merge, publish,
'* distribute, and/or sell copies of the Software, and to permit persons
'* to whom the Software is furnished to do so, provided that the above
'* copyright notice(s) and this permission notice appear in all copies of
'* the Software and that both the above copyright notice(s) and this
'* permission notice appear in supporting documentation.
'*
'* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
'* OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
'* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT
'* OF THIRD PARTY RIGHTS. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
'* HOLDERS INCLUDED IN THIS NOTICE BE LIABLE FOR ANY CLAIM, OR ANY SPECIAL
'* INDIRECT OR CONSEQUENTIAL DAMAGES, OR ANY DAMAGES WHATSOEVER RESULTING
'* FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT,
'* NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION
'* WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
'*
'* Disclaimer
'* ----------
'* Although reasonable care has been taken to ensure the correctness of this
'* implementation, this code should never be used in any application without
'* proper verification and testing. I disclaim all liability and responsibility
'* to any person or entity with respect to any loss or damage caused, or alleged
'* to be caused, directly or indirectly, by the use of this SNTPClient class.
'*
'* Comments, bugs and suggestions are welcome.
'*
'* Update history:
'* September 20, 2003
'* - Renamed the class from NTPClient to SNTPClient.
'* - Fixed the RoundTripDelay and LocalClockOffset properties.
'*   Thanks go to DNH <dnharris@csrlink.net>.
'* - Fixed the PollInterval property.
'*   Thanks go to Jim Hollenhorst <hollenho@attbi.com>.
'* - Changed the ReceptionTimestamp variable to DestinationTimestamp to follow the standard
'*   more closely.
'* - Precision property is now shown is seconds rather than milliseconds in the
'*   ToString method.
'* 
'* May 28, 2002
'* - Fixed a bug in the Precision property and the SetTime function.
'*   Thanks go to Jim Hollenhorst <hollenho@attbi.com>.
'* 
'* March 14, 2001
'* - First public release.
'*/

Imports System
Imports System.Net
Imports System.Net.Sockets
Imports System.Runtime.InteropServices
Imports System.Text

''' <summary>
''' Leap indicator field values
''' </summary>
''' <remarks>Indicator for leap second.</remarks>
Public Enum _LeapIndicator
    NoWarning       '0 - No warning
    LastMinute61    '1 - Last minute has 61 seconds
    LastMinute59    '2 - Last minute has 59 seconds
    Alarm           '3 - Alarm condition (clock not synchronized)
End Enum

''' <summary>
''' Mode field values
''' </summary>
Public Enum _Mode
    SymmetricActive     '1 - Symmetric active
    SymmetricPassive    '2 - Symmetric pasive
    Client              '3 - Client
    Server              '4 - Server
    Broadcast           '5 - Broadcast
    Unknown             '0, 6, 7 - Reserved
End Enum

''' <summary>
''' Stratum field values
''' </summary>
''' <remarks></remarks>
Public Enum _Stratum
    Unspecified         '0 - unspecified or unavailable
    PrimaryReference    '1 - primary reference (e.g. radio-clock)
    SecondaryReference  '2-15 - secondary reference (via NTP or SNTP)
    Reserved            '16-255 - reserved
End Enum


''' <summary>
''' SNTPClient is a VB.NET# class designed to connect to time servers on the Internet and
''' fetch the current date and time. Optionally, it may update the time of the local system.
''' The implementation of the protocol is based on the RFC 2030.
''' 
''' Public class members:
'''
''' LeapIndicator - Warns of an impending leap second to be inserted/deleted in the last
''' minute of the current day. (See the _LeapIndicator enum)
''' 
''' VersionNumber - Version number of the protocol (3 or 4).
''' 
''' Mode - Returns mode. (See the _Mode enum)
''' 
''' Stratum - Stratum of the clock. (See the _Stratum enum)
''' 
''' PollInterval - Maximum interval between successive messages
''' 
''' Precision - Precision of the clock
''' 
''' RootDelay - Round trip time to the primary reference source.
''' 
''' RootDispersion - Nominal error relative to the primary reference source.
''' 
''' ReferenceID - Reference identifier (either a 4 character string or an IP address).
''' 
''' ReferenceTimestamp - The time at which the clock was last set or corrected.
''' 
''' OriginateTimestamp - The time at which the request departed the client for the server.
''' 
''' ReceiveTimestamp - The time at which the request arrived at the server.
''' 
''' Transmit Timestamp - The time at which the reply departed the server for client.
''' 
''' RoundTripDelay - The time between the departure of request and arrival of reply.
''' 
''' LocalClockOffset - The offset of the local clock relative to the primary reference
''' source.
''' 
''' Initialize - Sets up data structure and prepares for connection.
''' 
''' Connect - Connects to the time server and populates the data structure.
'''	It can also update the system time.
''' 
''' IsResponseValid - Returns true if received data is valid and if comes from
''' a NTP-compliant time server.
''' 
''' ToString - Returns a string representation of the object.
''' 
''' -----------------------------------------------------------------------------
''' Structure of the standard NTP header (as described in RFC 2030)
'''                       1                   2                   3
'''   0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
'''  +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
'''  |LI | VN  |Mode |    Stratum    |     Poll      |   Precision   |
'''  +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
'''  |                          Root Delay                           |
'''  +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
'''  |                       Root Dispersion                         |
'''  +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
'''  |                     Reference Identifier                      |
'''  +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
'''  |                                                               |
'''  |                   Reference Timestamp (64)                    |
'''  |                                                               |
'''  +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
'''  |                                                               |
'''  |                   Originate Timestamp (64)                    |
'''  |                                                               |
'''  +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
'''  |                                                               |
'''  |                    Receive Timestamp (64)                     |
'''  |                                                               |
'''  +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
'''  |                                                               |
'''  |                    Transmit Timestamp (64)                    |
'''  |                                                               |
'''  +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
'''  |                 Key Identifier (optional) (32)                |
'''  +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
'''  |                                                               |
'''  |                                                               |
'''  |                 Message Digest (optional) (128)               |
'''  |                                                               |
'''  |                                                               |
'''  +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
''' 
''' -----------------------------------------------------------------------------
''' 
''' SNTP Timestamp Format (as described in RFC 2030)
'''                         1                   2                   3
'''     0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
''' +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
''' |                           Seconds                             |
''' +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
''' |                  Seconds Fraction (0-padded)                  |
''' +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
''' 
''' </summary>
''' 
Public Class SNTPClient

    '// NTP Data Structure Length
    Private Const SNTPDataLength As Byte = 47

    '// NTP Data Structure (as described in RFC 2030)
    Dim SNTPData(SNTPDataLength) As Byte

    '// Offset constants for timestamps in the data structure
    Private Const offReferenceID As Byte = 12
    Private Const offReferenceTimestamp As Byte = 16
    Private Const offOriginateTimestamp As Byte = 24
    Private Const offReceiveTimestamp As Byte = 32
    Private Const offTransmitTimestamp As Byte = 40

    '// The URL of the time server we're connecting to
    Private TimeServer As String

    '// SYSTEMTIME structure used by SetSystemTime
    <StructLayout(LayoutKind.Sequential)> Private Structure SYSTEMTIME
        Public year As Short
        Public month As Short
        Public dayOfWeek As Short
        Public day As Short
        Public hour As Short
        Public minute As Short
        Public second As Short
        Public milliseconds As Short
    End Structure

    <DllImport("KERNEL32.DLL", EntryPoint:="SetLocalTime", SetLastError:=True, CharSet:=CharSet.Unicode, ExactSpelling:=False, CallingConvention:=CallingConvention.StdCall)> _
    Private Shared Function SetLocalTime(ByRef time As SYSTEMTIME) As Integer
    End Function

    '// Destination Timestamp
    Public DestinationTimestamp As DateTime

    Public Shared TimeServers() As String = New String() {"time.nist.gov", "time-a.nist.gov", "time-b.nist.gov", "time-nw.nist.gov", "time-a.timefreq.bldrdoc.gov", "time-b.timefreq.bldrdoc.gov", "time-c.timefreq.bldrdoc.gov", "utcnist.colorado.edu", "nist1.symmetricom.com", "nist1-dc.glassey.com", "nist1-ny.glassey.com", "nist1-sj.glassey.com", "nist1.aol-ca.truetime.com", "nist1.aol-va.truetime.com", "nist1.columbiacountyga.gov", "nist.expertsmi.com"}

#Region " Class Events "
    Public Sub New()
        MyClass.New(TimeServers(1))
    End Sub

    Public Sub New(ByVal host As String)
        TimeServer = host
    End Sub
#End Region

#Region " Class Properties "

    ''' <summary>
    ''' Gets the leap indicator.
    ''' </summary>
    ''' <value>The leap indicator.</value>
    Public ReadOnly Property LeapIndicator() As _LeapIndicator
        Get
            'Isolate the two most significant bits
            Dim bVal As Byte = (SNTPData(0) >> 6)
            Select Case bVal
                Case 0 : Return _LeapIndicator.NoWarning
                Case 1 : Return _LeapIndicator.LastMinute61
                Case 2 : Return _LeapIndicator.LastMinute59
                Case 3 : Return _LeapIndicator.Alarm
                Case Else : Return _LeapIndicator.Alarm
            End Select
        End Get
    End Property

    ''' <summary>
    ''' Gets the version number.
    ''' </summary>
    ''' <value>The version number.</value>
    Public ReadOnly Property VersionNumber() As Byte
        Get
            'Isolate bits 3 - 5
            Dim bVal As Byte = CByte((SNTPData(0) And &H38) >> 3)
            Return bVal
        End Get
    End Property

    ''' <summary>
    ''' Gets the mode.
    ''' </summary>
    ''' <value>The mode.</value>
    Public ReadOnly Property Mode() As _Mode
        Get
            'Isolate bits 0 - 3
            Dim bVal As Byte = CByte((SNTPData(0) And &H7))
            Select Case bVal
                Case 0, 6, 7
                    Return _Mode.Unknown
                Case 1
                    Return _Mode.SymmetricActive
                Case 2
                    Return _Mode.SymmetricPassive
                Case 3
                    Return _Mode.Client
                Case 4
                    Return _Mode.Server
                Case 5
                    Return _Mode.Broadcast
            End Select
        End Get
    End Property

    ''' <summary>
    ''' Gets the stratum.
    ''' </summary>
    ''' <value>The stratum.</value>
    Public ReadOnly Property Stratum() As _Stratum
        Get
            Dim bVal As Byte = SNTPData(1)
            If (bVal = 0) Then
                Return _Stratum.Unspecified
            ElseIf (bVal = 1) Then
                Return _Stratum.PrimaryReference
            ElseIf (bVal <= 15) Then
                Return _Stratum.SecondaryReference
            Else
                Return _Stratum.Reserved
            End If
        End Get
    End Property

    ''' <summary>
    ''' Gets the poll interval.
    ''' </summary>
    ''' <value>The poll interval.</value>
    Public ReadOnly Property PollInterval() As Integer
        Get
            '// Thanks to Jim Hollenhorst <hollenho@attbi.com>
            Return CInt(Math.Pow(2, SNTPData(2)))
            'Return Math.Round(Math.Pow(2, SNTPData(2)))
        End Get
    End Property

    ''' <summary>
    ''' Gets the precision (in milliseconds)
    ''' </summary>
    ''' <value>The precision.</value>
    Public ReadOnly Property Precision() As Double
        Get
            '// Thanks to Jim Hollenhorst <hollenho@attbi.com>
            Return Math.Pow(2, SNTPData(3))
            'Return (1000 * Math.Pow(2, SNTPData(3) - 256))
        End Get
    End Property

    ''' <summary>
    ''' Gets the root delay (in milliseconds)
    ''' </summary>
    ''' <value>The root delay.</value>
    Public ReadOnly Property RootDelay() As Double
        Get
            Dim temp As Long = 0
            temp = 256 * (256 * (256 * SNTPData(4) + SNTPData(5)) + SNTPData(6)) + SNTPData(7)
            Return 1000 * ((temp) / &H10000)
        End Get
    End Property

    ''' <summary>
    ''' Gets the root dispersion (in milliseconds)
    ''' </summary>
    ''' <value>The root dispersion.</value>
    Public ReadOnly Property RootDispersion() As Double
        Get
            Dim temp As Long = 0
            temp = 256 * (256 * (256 * SNTPData(8) + SNTPData(9)) + SNTPData(10)) + SNTPData(11)
            Return 1000 * ((temp) / &H10000)
        End Get
    End Property

    ''' <summary>
    ''' Gets the reference identifier
    ''' </summary>
    ''' <value>The reference ID.</value>
    Public ReadOnly Property ReferenceID() As String
        Get
            Dim val As String = ""
            Select Case Stratum
                Case _Stratum.PrimaryReference, _Stratum.Unspecified
                    If SNTPData(offReferenceID + 0) <> 0 Then val += Chr(SNTPData(offReferenceID + 0))
                    If SNTPData(offReferenceID + 1) <> 0 Then val += Chr(SNTPData(offReferenceID + 1))
                    If SNTPData(offReferenceID + 2) <> 0 Then val += Chr(SNTPData(offReferenceID + 2))
                    If SNTPData(offReferenceID + 3) <> 0 Then val += Chr(SNTPData(offReferenceID + 3))
                Case _Stratum.SecondaryReference
                    Select Case VersionNumber
                        Case 3 '// Version 3, Reference ID is an IPv4 address
                            Dim Address As String = SNTPData(offReferenceID + 0).ToString() + "." + SNTPData(offReferenceID + 1).ToString() + "." + SNTPData(offReferenceID + 2).ToString() + "." + SNTPData(offReferenceID + 3).ToString()
                            Try
                                Dim Host As IPHostEntry = Dns.GetHostEntry(Address)
                                val = Host.HostName + " (" + Address + ")"
                            Catch e As Exception
                                val = "N/A"
                            End Try
                        Case 4 '// Version 4, Reference ID is the timestamp of last update
                            Dim time As DateTime = ComputeDate(GetMilliSeconds(offReferenceID))
                            '// Take care of the time zone
                            Dim offspan As TimeSpan = TimeZone.CurrentTimeZone.GetUtcOffset(DateTime.Now)
                            val = (time.Add(offspan)).ToString()
                        Case Else
                            val = "N/A"
                    End Select
            End Select
            Return val
        End Get
    End Property

    ''' <summary>
    ''' Gets the reference timestamp.
    ''' </summary>
    ''' <value>The reference timestamp.</value>
    Public ReadOnly Property ReferenceTimestamp() As DateTime
        Get
            Dim time As DateTime = ComputeDate(GetMilliSeconds(offReferenceTimestamp))
            '// Take care of the time zone
            Dim offspan As TimeSpan = TimeZone.CurrentTimeZone.GetUtcOffset(DateTime.Now)
            Return time.Add(offspan)
        End Get
    End Property

    ''' <summary>
    ''' Gets the originate timestamp.
    ''' </summary>
    ''' <value>The originate timestamp.</value>
    Public ReadOnly Property OriginateTimestamp() As DateTime
        Get
            Return ComputeDate(GetMilliSeconds(offOriginateTimestamp))
        End Get
    End Property

    ''' <summary>
    ''' Gets the receive timestamp.
    ''' </summary>
    ''' <value>The receive timestamp.</value>
    Public ReadOnly Property ReceiveTimestamp() As DateTime
        Get
            Dim time As DateTime = ComputeDate(GetMilliSeconds(offReceiveTimestamp))
            'Take care of the time zone
            Dim offspan As TimeSpan = TimeZone.CurrentTimeZone.GetUtcOffset(DateTime.Now)
            Return time.Add(offspan)
        End Get
    End Property

    ''' <summary>
    ''' Gets the transmit timestamp.
    ''' </summary>
    ''' <value>The transmit timestamp.</value>
    Public Property TransmitTimestamp() As DateTime
        Get
            Dim time As DateTime = ComputeDate(GetMilliSeconds(offTransmitTimestamp))
            'Take care of the time zone
            Dim offspan As TimeSpan = TimeZone.CurrentTimeZone.GetUtcOffset(DateTime.Now)
            Return time.Add(offspan)
        End Get
        Private Set(ByVal Value As DateTime)
            SetDate(offTransmitTimestamp, Value)
        End Set
    End Property

    ''' <summary>
    ''' Gets the round trip delay (in milliseconds)
    ''' </summary>
    ''' <value>The round trip delay.</value>
    Public ReadOnly Property RoundTripDelay() As Long
        Get
            '// Thanks to DNH <dnharris@csrlink.net>
            Dim span As TimeSpan = DestinationTimestamp.Subtract(OriginateTimestamp).Subtract(ReceiveTimestamp.Subtract(TransmitTimestamp))
            Return CLng(span.TotalMilliseconds)
        End Get
    End Property

    ''' <summary>
    ''' Gets the local clock offset (in milliseconds)
    ''' </summary>
    ''' <value>The local clock offset.</value>
    Public ReadOnly Property LocalClockOffset() As Long
        Get
            '// Thanks to DNH <dnharris@csrlink.net>
            Dim span As TimeSpan = ReceiveTimestamp.Subtract(OriginateTimestamp).Add((TransmitTimestamp.Subtract(DestinationTimestamp)))
            Return CLng(span.TotalMilliseconds / 2)
        End Get
    End Property

#End Region

#Region " Class Methods "

    '// Connect to the time server and update system time
    <System.Runtime.CompilerServices.MethodImpl(Runtime.CompilerServices.MethodImplOptions.Synchronized)> _
    Public Function Connect(ByVal UpdateSystemTime As Boolean) As Boolean
        Dim l_retval As Boolean

        l_retval = True
        Try
            'Resolve server address
            Dim hostadd As IPHostEntry = Dns.GetHostEntry(TimeServer)
            Dim EPhost As IPEndPoint = New IPEndPoint(hostadd.AddressList(0), 123)

            'Connect the time server
            Dim TimeSocket As UdpClient = New UdpClient
            TimeSocket.Connect(EPhost)

            'Initialize data structure
            Initialize()
            TimeSocket.Send(SNTPData, SNTPData.Length)
            'My.Application.DoEvents()
            System.Threading.Thread.Sleep(3000)
            SNTPData = TimeSocket.Receive(EPhost)
            If Not IsResponseValid() Then
                'Throw New Exception("Invalid response from " + TimeServer)
                l_retval = False
            End If
            DestinationTimestamp = DateTime.Now
        Catch e As SocketException
            'Throw New Exception(e.Message)
            l_retval = False
        End Try

        '// Update system time
        If UpdateSystemTime Then SetTime()
        Return l_retval
    End Function

    '// Check if the response from server is valid
    Public Function IsResponseValid() As Boolean
        If (SNTPData.Length < SNTPDataLength Or Mode <> _Mode.Server) Then
            Return False
        Else
            Return True
        End If
    End Function

    ''' <summary>
    ''' Returns a <see cref="T:System.String"></see> that represents the differant properties of the current class.
    ''' </summary>
    ''' <returns>
    ''' A <see cref="T:System.String"></see> that represents the current <see cref="SNTPClient"></see>.
    ''' </returns>
    Public Overrides Function ToString() As String
        Dim sb As New StringBuilder("")

        sb.Append("Leap Indicator: ")
        Select Case LeapIndicator
            Case _LeapIndicator.NoWarning
                sb.Append("No warning")
            Case _LeapIndicator.LastMinute61
                sb.Append("Last minute has 61 seconds")
            Case _LeapIndicator.LastMinute59
                sb.Append("Last minute has 59 seconds")
            Case _LeapIndicator.Alarm
                sb.Append("Alarm Condition (clock not synchronized)")
        End Select
        sb.Append(vbCrLf & "Version number: " + VersionNumber.ToString())
        sb.Append(vbCrLf & "Mode: ")
        Select Case Mode
            Case _Mode.Unknown
                sb.Append("Unknown")
            Case _Mode.SymmetricActive
                sb.Append("Symmetric Active")
            Case _Mode.SymmetricPassive
                sb.Append("Symmetric Pasive")
            Case _Mode.Client
                sb.Append("Client")
            Case _Mode.Server
                sb.Append("Server")
            Case _Mode.Broadcast
                sb.Append("Broadcast")
        End Select
        sb.Append(vbCrLf & "Stratum: ")
        Select Case Stratum
            Case _Stratum.Unspecified
            Case _Stratum.Reserved
                sb.Append("Unspecified")
            Case _Stratum.PrimaryReference
                sb.Append("Primary Reference")
            Case _Stratum.SecondaryReference
                sb.Append("Secondary Reference")
        End Select
        sb.Append(vbCrLf & "Current Computer time: " + DateTime.Now.ToString("MM/dd/yyyy hh:mm:ss.ffff tt"))
        sb.Append(vbCrLf & "Local (Transmit) time: " + TransmitTimestamp.ToString("MM/dd/yyyy hh:mm:ss.ffff tt"))
        sb.Append(vbCrLf & "Original time: " + OriginateTimestamp.ToString("MM/dd/yyyy hh:mm:ss.ffff tt"))
        sb.Append(vbCrLf & "Receive time: " + ReceiveTimestamp.ToString("MM/dd/yyyy hh:mm:ss.ffff tt"))
        sb.Append(vbCrLf & "Reference time: " + ReferenceTimestamp.ToString("MM/dd/yyyy hh:mm:ss.ffff tt"))
        sb.Append(vbCrLf & "Precision: " + Precision.ToString() + " ms")
        sb.Append(vbCrLf & "Poll Interval: " + PollInterval.ToString() + " s")
        sb.Append(vbCrLf & "Reference ID: " + ReferenceID.ToString())
        sb.Append(vbCrLf & "Root Delay: " + RootDelay.ToString() + " ms")
        sb.Append(vbCrLf & "Root Dispersion: " + RootDispersion.ToString() + " ms")
        sb.Append(vbCrLf & "Round Trip Delay: " + RoundTripDelay.ToString() + " ms")
        sb.Append(vbCrLf & "Local Clock Offset: " + LocalClockOffset.ToString() + " ms")
        sb.Append(vbCrLf)

        Return sb.ToString
    End Function
#End Region

#Region " Class Private Methods "

    '// Initialize the NTPClient data
    Private Sub Initialize()
        'Set version number to 4 and Mode to 3 (client)
        SNTPData(0) = &H1B
        'Initialize all other fields with 0
        Dim i As Integer
        For i = 1 To 47
            SNTPData(i) = 0
        Next
        'Initialize the transmit timestamp
        TransmitTimestamp = DateTime.Now
    End Sub

    '// Compute date, given the number of milliseconds since January 1, 1900
    Private Function ComputeDate(ByVal milliseconds As Decimal) As DateTime
        Dim span As TimeSpan = TimeSpan.FromMilliseconds(milliseconds)
        Dim time As DateTime = New DateTime(1900, 1, 1)
        time = time.Add(span)
        Return time
    End Function

    '// Compute the number of milliseconds, given the offset of a 8-byte array
    Private Function GetMilliSeconds(ByVal offset As Byte) As Decimal
        Dim intPart As Decimal = 0, fractPart As Decimal = 0
        Dim i As Integer
        For i = 0 To 3
            intPart = Int(256 * intPart + SNTPData(offset + i))
        Next
        For i = 4 To 7
            fractPart = Int(256 * fractPart + SNTPData(offset + i))
        Next
        Dim milliseconds As Decimal = Int(intPart * 1000 + (fractPart * 1000) / &H100000000L)
        Return milliseconds
    End Function

    '// Compute the 8-byte array, given the date
    Private Sub SetDate(ByVal offset As Byte, ByVal dateval As DateTime)
        Dim intPart As Decimal = 0, fractPart As Decimal = 0
        Dim StartOfCentury As DateTime = New DateTime(1900, 1, 1, 0, 0, 0)
        Dim milliseconds As Decimal = CDec(Math.Round(dateval.Subtract(StartOfCentury).TotalMilliseconds, MidpointRounding.ToEven))
        intPart = Int(milliseconds / 1000)
        fractPart = Int(((milliseconds Mod 1000) * &H100000000L) / 1000)
        Dim temp As Decimal = intPart
        Dim i As Decimal
        For i = 3 To 0 Step -1
            SNTPData(CInt(offset + i)) = CByte(temp Mod 256)
            temp = Int(temp / 256)
        Next
        temp = Int(fractPart)
        For i = 7 To 4 Step -1
            SNTPData(CInt(offset + i)) = CByte(temp Mod 256)
            temp = Int(temp / 256)
        Next
    End Sub

    '// Set system time according to transmit timestamp
    Private Sub SetTime()
        Dim st As SYSTEMTIME
        Dim trts As DateTime = DateTime.Now.AddMilliseconds(LocalClockOffset)
        st.year = CShort(trts.Year)
        st.month = CShort(trts.Month)
        st.dayOfWeek = CShort(trts.DayOfWeek)
        st.day = CShort(trts.Day)
        st.hour = CShort(trts.Hour)
        st.minute = CShort(trts.Minute)
        st.second = CShort(trts.Second)
        st.milliseconds = CShort(trts.Millisecond)
        SetLocalTime(st)
    End Sub
#End Region

End Class
