SNTP client

As of PowerNet v4.30 (8 May 2008) the user interface to the SNTP data has changed and is not compatible with the previous one.

The code in <PNet>\SNTP implements most of an SNTP client according to RFC1361 and RFC4330. See also RFC1305. Do not attempt to modify this code until you have absorbed these RFCs.

If you do not have an NTP server on your network, there are "public" ones available. See www.ntp.org for details. Note when testing that advertised public servers are not always available. Your ISP probably maintains a functioning NTP server.

0 equ SNTPdebug?        \ -- x
Set this non-zero to display SNTP debug information during execution.

1 equ SNTPactions?      \ -- x
Defined the actions performed by default for timestamps.


 0     Dummy action
 1     Set clock
 ...   user defined action

SNTP equates and structure

#123 equ SNTPport#      \ -- port#
The standard SNTP port number. The client (us) and the server use the same port number.

STRUCT /sntp    \ -- len
SNTP payload structure after the IP and UDP headers. The Timestamp fields contain a 32.32-bit fractional time in network (big-endian) format. The first cell contains seconds, measured since 1st Jan 1900, as per RFC1361. The second cell contains a fractional second.

If you are using the SNTP code with the Unix calendar code in Examples/UnixTime.fth\, NTP timestamps are based from 1 Jan 1900, which has an LSECONDS value of 1291876096+86400=1291962496, so add 1291962496 to the NTP seconds value to produce a Unix time value.

If you are using this code with NTP timestamps, these are based from 1 Jan 1900, which has an LSECONDS value of 1291876096+86400=1291962496, so add 1291962496 to the NTP seconds value to produce a Unix time value. Note that the Unix LSECONDS counter rolls over in 2036. Suitable code to convert LSECONDS to time and date can be found in your cross compiler Examples/UnixTime.fth.

If you are planning a product which needs to cope with dates beyond 2035, note the following from RFC4330.

As the NTP timestamp format has been in use for over 20 years, it is possible that it will be in use 32 years from now, when the seconds field overflows. As it is probably inappropriate to archive NTP timestamps before bit 0 was set in 1968, a convenient way to extend the useful life of NTP timestamps is the following convention: If bit 0 is set, the UTC time is in the range 1968- 2036, and UTC time is reckoned from 0h 0m 0s UTC on 1 January 1900. If bit 0 is not set, the time is in the range 2036-2104 and UTC time is reckoned from 6h 28m 16s UTC on 7 February 2036. Note that when calculating the correspondence, 2000 is a leap year, and leap seconds are not included in the reckoning.

SNTP Configuration

struct /SNTPparams              \ -- len
A set of parameters to configure the SNTP service This structure is the simplest way to interact with the SNTP module. The elements have the following meaings :-

sp.SNTPserver

- the IPv4 address of the SNTP server to use.

sp.PollInterval

- log2 of the interval between polls in seconds, e.g. 5 for a 32 second interval. This value is set to 5 by default, and you should probably increase it when you are satisfied with your SNTP handling.

*\fo{sp.xtSetTime

- the xt of a callback to yourapplication to process the received SNTP packet. This iscalled whenever an SNTP packet is received.The word receives the address and length of the UDP data,i.e. a /SNTP structure described below, and shouldreturn nothing ( caddr len -- ).Note that the data is in a pbuff that is owned bythe SNTP engine and will be discarded. Copy required dataout of the /SNTP structure.

sp.xtGetTime

- an optional configuration, and isnot required for pure SNTP. If set, it must put the currenttimestamp on the stack as a 32.32-bit fractional RFC1361 time.The stack effect is ( -- TimeLo TimeHi ).The default action is a word that returns a zero timestamp,which is SNTP-compliant behaviour. This configuration optionis supplied to give the system better resolution if the clockcontroller can generate an NTP-compliant timestamp.

  int sp.SNTPserver     \ IP address of the server to use. Must be first.
  int sp.PollInterval   \ Time between SNTP polls, in seconds, as a power of two
  int sp.xtSetTime      \ Callback to alert caller to a new NTP packet
  int sp.xtGetTime      \ xt of word to provide current timestamp, or zero if n/a
end-struct

idata create SNTPparams \ -- addr
The /SNTPparams parameter block in IDATA space (RAM). Note: This layout must match the layout of the /SNTPparams structure above. Patch this table with the actions you supply.

SNTPparams equ SNTPserver
Address holding SNTP server IP address.

SNTP state machine

The SNTP system is controlled by a state machine, which is run whenever the timer *\fo{SNTPtimer) times out. The action performed is responsible for setting the next state and updating the timer. The state machine actions are performed by SNTPidle ( -- ) which is called in the service task.

States of the SNTP state machine. See RFC1361


 -------                                   ------------
|       |                                 |            |
| INIT- |----Parameter set from caller--->| CONFIGURED |<-------+
| REBOOT|                             +---|            |        |
|       |                             |    ------------         |
 -------                              |         |               |
                                      |   NTPREQUEST Sent       |
                         NTP Packet Received    |               |
                                      |         +---------------+
                                  --------                      ^
                                 |        |                     |
                                 | ACTIVE |--- Timeout----------+
                                 |        |
                                  --------
[sm
  smState smSNinit              \ initialisation, wait for params from caller
  smState smSNconfigured        \ Send SNTP Request, set up timeout
  smState smSNactive            \ SNTP answer received within timeout period
sm] #smSNactions                \ number of states in this machine

variable SNTPTimer      \ -- addr
Holds the TICKS time at which the next SNTP action will be performed, 0 for no action.

variable smSNcount      \ -- addr
Down counter used to provide repetitions and timeouts.

variable smSNstate      \ -- n
Holds current SNTP state.

create smSNactions      \ -- addr
Execution table containing the xts of the action words. This in CDATA space and is filled in later.

: setSNstate    \ xt state --
INTERPRETER word that sets the action of the given state.

: SNTPidle      \ --
Performed periodically to do SNTP actions.

State machine utilities

: SetSNTPtimer  \ ms --
Set the SNTP timer to time out ms later.

: .SNstate      \ --
Debug tool to display the current state of the SNTP state machine.

: toSNTPstate   \ ms n state --
The given state will be executed after the given period in ms and n times if sensitive to the number of times. Note that this specifies the interval until the state is executed, not the repetition rate of the state.

Outgoing message tools

variable SNTPreply?     \ -- addr
Set non-zero when an SNTP reply has been received, but the data has not yet been been processed. During this time any following SNTP packets will be discarded.

: (SNTPPSend)   { *pb ipaddr -- }
Send SNTP data as a UDP packet. The parameters are *pb, a pointer to a PBuff containing the UDP data, and ipaddr, the IP address to send to.

: SendSNTP      { | *pb *sntp -- }
Send an SNTP message.

SNTP state selection

Each state is controlled by three values defined here.

: goSNTPInit    \ --
Go to the smSNnit state - stop SNTP activity and wait for parameters from the calling app.

: goSNTPConfigured      \ --
Go to the smSNconfigured state; start the SNTP process.

: goSNTPActive  \ --
Go to the smSNactive state - SNTP data has been received

: doSNinit      \ --
SNTPauto?=nz: Do nothing until SNTP server has been set, either manually or by DHCP, and then start SNTP.

: doSNconfigured        \ --
"Configured" state has timed out - send SNTP request and wait for response or timeout

: doSNactive    \ --
"active" state has timed out; revert to "configured"

: runSNTP       \ --
Start the SNTP process and wait until a valid time has been returned.

Receive SNTP packet

: RxSNTP        \ *pb --
Process received SNTP packets. When we get here we know this is from port 123 and that the Ethernet packet was either addressed to us or was broadcast.

create SNTPServiceStruct        \ -- addr
Holds the UDP Service info to run the SNTP code on receipt of SNTP packets, or idle on timeout. Not that this must match the /UDPService structure in UDP.fth.

Set up state machine

  ' doSNinit            smSNinit        setSNstate
  ' doSNconfigured      smSNconfigured  setSNstate
  ' doSNactive          smSNactive      setSNstate