Network Time Protocol NTP SNTP
Posted: Sun Nov 22, 2009 2:36 am
Here is a piece of code I am in the process of writing which some of you may find useful in your projects where you may wish to access a network time server
The code is not complete, but I figured is comprehensive enough to place here in tips & tricks.
1 flaw in my code I know of is that I have not worked up any way of getting fractions of seconds from the PB unix timestamp.
I have not tried to do this yet & do not know if it is even possible.
Small modification made to EncodeNTPVersionModeRequest(Version.a,Mode.a) procedure. 22/11/2009
Updated to use Rescators Endian procedure. Look here for details on Rescators code:
http://www.purebasic.fr/english/viewtop ... 12&t=14524
Updated with a little more information. 24/01/2010
The code is not complete, but I figured is comprehensive enough to place here in tips & tricks.

1 flaw in my code I know of is that I have not worked up any way of getting fractions of seconds from the PB unix timestamp.
I have not tried to do this yet & do not know if it is even possible.
Small modification made to EncodeNTPVersionModeRequest(Version.a,Mode.a) procedure. 22/11/2009
Code: Select all
; SNTP client as a helper for PB community people wishing for an accurate timestamp in projects
; 22/11/2009
; Updated 24/01/2010
; Baldrick
; PB4.40, 4.41RC
;{ NTP DataGram Structure diagram
;-NTP structure diagram
; 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
; +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
; 0|LI | VN |Mode | Stratum | Poll | Precision |
; +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
; 4| Root Delay (32) |
; +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
; 8| Root Dispersion (32) |
; +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
;12| Reference Identifier (32) |
; +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
;16| Ref_Timestamp1 |
;20| Ref_Timestamp2 Reference Timestamp (64) |
; +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
;24| Orig_Timestamp1 |
;28| Orig_Timestamp2 Originate Timestamp (64) |
; +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
;32| Rx_Timestamp1 |
;36| Rx_Timestamp2 Receive Timestamp (64) |
; +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
;40| Tx_Timestamp1 |
;44| Tx_Timestamp2 Transmit Timestamp (64) |
; +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
;48| Key Identifier (optional) (32) |
; +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
;52| Msg_Digest1 |
;56| Msg_Digest2 |
;60| Msg_Digest3 Message Digest (optional) (128) |
;64| Msg_Digest4 |68
; +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
;}
;{ Header detail:
;-Header Detail
; Leap Indicator
;This is a two-bit code warning of an impending leap second to be inserted/deleted
;in the last minute of the current day, with bit 0 and bit 1, respectively,
;LI Binary|Value Meaning:
;00 0 no warning
;01 1 last minute has 61 seconds
;10 2 last minute has 59 seconds)
;11 3 alarm condition (clock Not synchronized)
; -------------------------------------------------------------------------------
;
;Version
;This is a three-bit integer indicating the NTP/SNTP version number.
;The version number is 3 for Version 3 (IPv4 only) and 4 for Version 4
;(IPv4, IPv6 and OSI). If necessary to distinguish between IPv4, IPv6 and OSI,
;the encapsulating context must be inspected.
; --------------------------------------------------------------------------------
;
;
;Mode
;This is a three-bit integer indicating the mode, with values defined as follows:
;0 reserved
;1 symmetric active
;2 symmetric passive
;3 client
;4 server
;5 broadcast
;6 reserved For NTP control message
;7 reserved For private use
;
;In unicast and anycast modes, the client sets this field to 3 (client) in the
;request and the server sets it to 4 (server) in the reply.
;In multicast mode, the server sets this field To 5 (broadcast).
; -----------------------------------------------------------------------------------
;
;Stratum:
;This is a eight-bit unsigned integer indicating the stratum level of
;the local clock, with values defined as follows:
;0 unspecified or unavailable
;1 primary reference (e.g., radio clock)
;2-15 secondary reference (via NTP Or SNTP)
;16-255 reserved
; ------------------------------------------------------------------------------------
;
;Poll Interval:
;This is an eight-bit signed integer indicating the maximum
;interval between successive messages, in seconds to the nearest power of two.
;The values that can appear in this field presently range from 4 (16 s) to 14 (16284 s)
;however, most applications use only the sub-range 6 (64 s) to 10 (1024 s).
; --------------------------------------------------------------------------------------
;
;Precision:
;This is an eight-bit signed integer indicating the precision of the local clock,
;in seconds to the nearest power of two. The values that normally appear in this
;field range from -6 for mains-frequency clocks to -20 for microsecond clocks found
;in some workstations
; --------------------------------------------------------------------------------------
;
;Reference Identifier: This is a 32-bit string identifying the particular reference
;source. In the Case of NTP Version 3 Or Version 4 stratum-0 (unspecified) Or stratum-1
;(primary) servers, this is a four-character ASCII string, left justified And zero padded
;To 32 bits. In NTP Version 3 secondary servers, this is the 32-bit IPv4 address of the
;reference source. In NTP Version 4 secondary servers, this is the low order 32 bits of
;the latest transmit timestamp of the reference source.
; ---------------------------------------------------------------------------------------
;
;Reference Timestamp:
;This is the time at which the local clock was last set Or corrected,
;
;Originate Timestamp:
;This is the time at which the request departed the client
;For the server.
;
;Receive Timestamp:
;This is the time at which the request arrived at the server.
;
;Transmit Timestamp:
;This is the time at which the reply departed the server
;For the client.
; ---------------------------------------------------------------------------------------
; Originate Timestamp T1 time request sent by client
; Receive Timestamp T2 time request received by server
; Transmit Timestamp T3 time reply sent by server
; Destination Timestamp T4 time reply received by client
;
; The roundtrip delay d And local clock offset t are defined As
;
; d = (T4 - T1) - (T2 - T3) t = ((T2 - T1) + (T3 - T4)) / 2.
;
;}
#NTP_VERSION=3
#NTP_CLIENT=3
#NTP_PORT=123
#NTP_BUFFER_SIZE=48
#NTP_Unix_TimeStamp_Differential=$83AA7E80 ;thanks to Harkon
#SecondsPerMinte=60
Enumeration ;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)
EndEnumeration
Enumeration ;Mode
#Unknown ;0 - Reserved
#SymmetricActive ;1 - Symmetric active
#SymmetricPassive ;2 - Symmetric pasive
#Client ;3 - Client
#Server ;4 - Server
#Broadcast ;5 - Broadcast
#ReservedA ;6 - Reserved
#ReservedB ;7 - Reserved
EndEnumeration
;- Set your own UTC to local Time Offset here for non windows
CompilerSelect #PB_Compiler_OS
CompilerCase #PB_OS_Windows
Select GetTimeZoneInformation_(Tz.Time_zone_information)
Case #TIME_ZONE_ID_STANDARD
TimeZoneOffset=(Tz\Bias+Tz\StandardBias)
Case #TIME_ZONE_ID_DAYLIGHT
TimeZoneOffset=(Tz\Bias+Tz\DaylightBias)
EndSelect
; CompilerCase #PB_OS_Linux
; ;get local computers timezone bias & setup into offset minutes
; CompilerCase #PB_OS_AmigaOS
; ;get local computers timezone bias & setup into offset minutes
; CompilerCase #PB_OS_MacOS
; ;get local computers timezone bias & setup into offset minutes
CompilerDefault
TimeZoneOffset=-660 ;manually set to your own bias for non windows OS's in minutes
;fyi, -660 is my current Sydney Au timezone with Daylight time active @23/01/2010
CompilerEndSelect
Timeout.i=3000 ;milliseconds for network response timeout
;- Some ntp servers
; a list can be found here:
; http://support.ntp.org/bin/view/Servers/StratumTwoTimeServers
;ServerName$="au.pool.ntp.org"
ServerName$="202.60.94.15" ;=au.pool.ntp.org
;ServerName$="ntp.bri.connect.com.au"
;ServerName$="pool.ntp.org"
;ServerName$="0.pool.ntp.org"
;ServerName$="202.81.208.160" ;=0.pool.ntp.org
;ServerName$="time.windows.com"
Structure ntp_struct
Li_Vn_Mode.a ;ubyte sharing Leap indicator, Version number, Mode
stratum.a ;ubyte
poll_interval.b ;byte
precision.b ;byte
root_delay.l ;long
root_dispersion.l ;ulong
ref_id.l ;ulong
ref_timestamp1.l ;ulong
ref_timestamp2.l ;ulong
orig_timestamp1.l ;ulong
orig_timestamp2.l ;ulong
rx_timestamp1.l ;ulong
rx_timestamp2.l ;ulong
tx_timestamp1.l ;ulong
tx_timestamp2.l ;ulong
; key_id.l ;optional ulong
; msg_digest1.l ;optional ulong
; msg_digest2.l ;optional ulong
; msg_digest3.l ;optional ulong
; msg_digest4.l ;optional ulong
EndStructure
Ntp_Data.ntp_struct
Procedure ByteOrder32(Val.l)
*Mem=AllocateMemory(SizeOf(long))
If *Mem
a=0:b=24
PokeL(*Mem,Val)
Repeat
Result.l+PeekA(*Mem+a)<<b
a+1:b-8
Until a>3
FreeMemory(*Mem)
ProcedureReturn Result
EndIf
EndProcedure
Procedure.a EncodeNTPVersionModeRequest(Version.a,Mode.a) ;bits 1 & 2 are for leap indicator & need not be touched here
ProcedureReturn Version<<3+Mode
EndProcedure
Procedure EncodeNTPRequestTimeStamp(UNIX_Timestamp.l,LocalUTCOffsetMinutes.l)
TimeOffset=#SecondsPerMinte*LocalUTCOffsetMinutes
UNIX_Timestamp+TimeOffset
UNIX_Timestamp+#NTP_Unix_TimeStamp_Differential
ProcedureReturn ByteOrder32(UNIX_Timestamp)
EndProcedure
Procedure DecodeNTPTimestamp(NTP_Timestamp.l,NTP_Timestamp2.l,LocalUTCOffsetMinutes.l)
Result=ByteOrder32(NTP_Timestamp)+(ByteOrder32(NTP_Timestamp2)>>31)
ProcedureReturn (Result-#NTP_Unix_TimeStamp_Differential)-(#SecondsPerMinte*LocalUTCOffsetMinutes);TimeOffset
EndProcedure
Procedure DecodeTimestampNTPasUTC(NTP_Timestamp.l,NTP_Timestamp2.l)
Result=ByteOrder32(NTP_Timestamp)+(ByteOrder32(NTP_Timestamp2)>>31)
ProcedureReturn Result-#NTP_Unix_TimeStamp_Differential
EndProcedure
Procedure.a DecodeNTPLeapIndicator(HeaderByte.a) ;isolate & return value of Leap indicator bits
ProcedureReturn HeaderByte>>6 ;move 2 bits right to pos 1,2 for correct value
EndProcedure
Procedure.a DecodeNTPVersion(HeaderByte.a) ;isolate & return value of Version bits
HeaderByte<<2 ;move left 2 bits to drop leap indicator bits
ProcedureReturn HeaderByte>>5 ;move 3 remaining bits right to bit pos 1,2,3 to get wanted value
EndProcedure
Procedure.a DecodeNTPMode(HeaderByte.a) ; isolate & return value of Mode bits
HeaderByte<<5 ;move all unwanted bits off to left so they are lost
ProcedureReturn HeaderByte>>5 ;move wanted ramining 3 bits back to pos 1,2,3
EndProcedure
*membuffer=AllocateMemory(#NTP_BUFFER_SIZE)
;- Encode request
If *membuffer
PokeA(*membuffer,EncodeNTPVersionModeRequest(#NTP_VERSION,#NTP_CLIENT))
PokeL(*membuffer+40,EncodeNTPRequestTimeStamp(Date(),TimeZoneOffset))
CompilerSelect #PB_Compiler_OS
CompilerCase #PB_OS_Windows
If GetSystemTime_(sTime.systemtime) ;get Milliseconds from system direct
PokeL(*membuffer+44,ByteOrder32(sTime\wMilliseconds<<21)) ;shift 1st 10 bits
EndIf ;to highest order to match as close as possible to 32 bit fraction stamp
CompilerEndSelect
Else
Debug "membuffer Failed to allocate memory"
End
EndIf
IsNetwork=InitNetwork()
If Not IsNetwork
MessageRequester("Network","Initialisation fail",#MB_ICONERROR)
End
EndIf
If IsNetwork
ClientID=OpenNetworkConnection(ServerName$,#NTP_PORT,#PB_Network_UDP)
If Not ClientID
MessageRequester("Network","Connection to: [ "+ServerName$+" ] failed",#MB_ICONERROR)
End
Else
SendNetworkData(ClientID,*membuffer,#NTP_BUFFER_SIZE)
TimerA=ElapsedMilliseconds()
TimerA+Timeout
Debug "Connection to [ "+ServerName$+" ] opened, request sent"
Repeat
TimerB=ElapsedMilliseconds()
If NetworkClientEvent(ClientID)
DtRxd=1
If ReceiveNetworkData(ClientID,*membuffer,#NTP_BUFFER_SIZE)
With Ntp_Data
\Li_Vn_Mode=PeekA(*membuffer)
\stratum=PeekA(*membuffer+1)
\poll_interval=PeekA(*membuffer+2)
\precision=PeekA(*membuffer+3)
\root_delay=PeekL(*membuffer+4)
\root_dispersion=PeekL(*membuffer+8)
\ref_id=PeekL(*membuffer+12)
\ref_timestamp1=PeekL(*membuffer+16)
\ref_timestamp2=PeekL(*membuffer+20)
\orig_timestamp1=PeekL(*membuffer+24)
\orig_timestamp2=PeekL(*membuffer+28)
\rx_timestamp1=PeekL(*membuffer+32)
\rx_timestamp2=PeekL(*membuffer+36)
\tx_timestamp1=PeekL(*membuffer+40)
\tx_timestamp2=PeekL(*membuffer+44)
EndWith
EndIf
EndIf
Delay(1)
Until TimerB>TimerA Or DtRxd
If Not DtRxd
MessageRequester("Timeout", "No response from [ "+ServerName$+" ]",#MB_ICONEXCLAMATION)
EndIf
EndIf
EndIf
If DtRxd
With Ntp_Data
Debug "---Header information:---------------------------------------------------"
Debug "Li_Vn_Mode raw byte value = "+Str(\Li_Vn_Mode )
Select DecodeNTPLeapIndicator(\Li_Vn_Mode)
Case #NoWarning
Debug "Leap indicator = no warning"
Case #LastMinute61
Debug "Leapindicator = Last minute has 61 seconds"
Case #LastMinute59
Debug "Leap indicator = Last minute has 59 seconds"
Case #Alarm
Debug "Leap indicator = Alarm condition (clock Not synchronized)"
EndSelect
Debug "Version = "+Str(DecodeNTPVersion(\Li_Vn_Mode))
Select DecodeNTPMode(\Li_Vn_Mode)
Case #Unknown,#ReservedA,#ReservedB
Debug "Mode message = Value Reserved"
Case #SymmetricActive
Debug "Mode message = Symmetric Active"
Case #SymmetricPassive
Debug "Mode message = Symmetric Passive"
Case #Client
Debug "Mode message = Client"
Case #Server
Debug "Mode message = Server"
Case #Broadcast
Debug "Mode message = Broadcast"
EndSelect
Debug "stratum = "+Str(\stratum)
Debug "poll_interval = "+Str(\poll_interval)
Debug "precision = "+Str(\precision)
Debug "--root info still To Do--------------------------------------------------"
Debug "root_delay = "+Str(\root_delay)
Debug "root_dispersion = "+Str(\root_dispersion)
Debug "----Reference IP Address-------------------------------------------------"
Debug "ref_id = "+Str(\ref_id)
Debug "Ref IP Address = "+IPString(\ref_id)
Debug "A reverse DNS lookup could now be done using Freaks code here:"
Debug "http://www.purebasic.fr/english/viewtopic.php?f=12&t=25916&p=182886"
Debug "----Server timestamp was last syncronized @-UTC--------------------------"
Debug "ref_timestamp1 = "+Str(\ref_timestamp1)
Debug "ref_timestamp2 = "+Str(\ref_timestamp2)
Debug "Reference Timestamp = "+FormatDate("%hh:%ii:%ss - %dd/%mm/%yyyy",DecodeTimestampNTPasUTC(\ref_timestamp1,\ref_timestamp2))
Debug "---Timestamp originating from local computer--UTC----------------------"
Debug "orig_timestamp1 = "+Str(\orig_timestamp1)
Debug "orig_timestamp2 = "+Str(\orig_timestamp2)
Debug "Originating Timestamp = "+FormatDate("%hh:%ii:%ss - %dd/%mm/%yyyy",DecodeTimestampNTPasUTC(\orig_timestamp1,\orig_timestamp2))
Debug "-------------------------------------------------------------------------"
Debug "rx_timestamp1 = "+Str(\rx_timestamp1)
Debug "rx_timestamp2 = "+Str(\rx_timestamp2)
Debug "tx_timestamp1 = "+Str(\tx_timestamp1)
Debug "tx_timestamp2 = "+Str(\tx_timestamp2)
Debug "Current time, including localised timezone offset of "+StrF(-TimeZoneOffset/60,2)+" Hours or "+Str(-TimeZoneOffset)+" minutes"
Debug FormatDate("%hh:%ii:%ss - %dd/%mm/%yyyy",DecodeNTPTimestamp(\tx_timestamp1,\tx_timestamp2,TimeZoneOffset))
Debug "As UTC time:"
Debug FormatDate("%hh:%ii:%ss - %dd/%mm/%yyyy",DecodeTimestampNTPasUTC(\tx_timestamp1,\tx_timestamp2))
Debug "-------------------------------------------------------------------------"
EndWith
;{ This part not yet finished.( Something wrong with my calculations here)
Ms1K=$FFFFFFFF/$3E8
a.f= (NTP_Data\tx_timestamp2/Ms1K)
tx.l=a
a.f= (NTP_Data\rx_timestamp2/Ms1K)
rx.l=a
delay=tx-rx
If delay<0 ;should not be neccesary ??
delay=-delay
EndIf
Debug "server processing delay = "+Str(delay)+" milliseconds"
Debug ""
;}
EndIf
If ClientID
CloseNetworkConnection(ClientID)
EndIf
If *membuffer
FreeMemory(*membuffer)
EndIf
End
Updated to use Rescators Endian procedure. Look here for details on Rescators code:
http://www.purebasic.fr/english/viewtop ... 12&t=14524
Updated with a little more information. 24/01/2010