Travaux Pratiques - NTP Network Time Protocol - version 100% Amiga
C'est pratiquement le même programme que celui-ci, mais en se passant du time() de la bibliothèque C standard. Au lieu de compter à partir du 1er janvier 1970, il faudra le faire à partir du 1er janvier 1978.

Petit programme
Pour illustrer le fonctionnement d'un client NTP, on va comparer la date du système avec la date du serveur pool.ntp.org
Il devrait compiler avec le SASC 6.58 ou gcc et le netinclude de l'AmigaOS4.0

/* ntp.c */
/* Amiga version */

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#include <clib/alib_protos.h>

#include <proto/exec.h>
#include <proto/dos.h>
#include <proto/bsdsocket.h>
#include <proto/locale.h>
#include <proto/timer.h>

#define NTP_TIMESTAMP_DELTA_1970 2208988800UL
#define NTP_TIMESTAMP_DELTA_1978 2461449600UL 

/* Structure that defines the 48 byte NTP packet protocol. */
typedef struct
{

  UBYTE li_vn_mode ;     /* Eight bits. li, vn, and mode.*/
                         /* li.   Two bits.   Leap indicator.*/
                         /* vn.   Three bits. Version number of the protocol.*/
                         /* mode. Three bits. Client will pick mode 3 for client.*/

  UBYTE stratum ;        /* Eight bits. Stratum level of the local clock.*/
  UBYTE poll ;           /* Eight bits. Maximum interval between successive messages.*/
  UBYTE precision ;      /* Eight bits. Precision of the local clock.*/

  ULONG rootDelay ;      /* 32 bits. Total round trip delay time.*/
  ULONG rootDispersion ; /* 32 bits. Max error aloud from primary clock source.*/
  ULONG refId ;          /* 32 bits. Reference clock identifier.*/

  ULONG refTm_s ;        /* 32 bits. Reference time-stamp seconds.*/
  ULONG refTm_f ;        /* 32 bits. Reference time-stamp fraction of a second.*/

  ULONG origTm_s ;       /* 32 bits. Originate time-stamp seconds.*/
  ULONG origTm_f ;       /* 32 bits. Originate time-stamp fraction of a second.*/

  ULONG rxTm_s ;         /* 32 bits. Received time-stamp seconds.*/
  ULONG rxTm_f ;         /* 32 bits. Received time-stamp fraction of a second.*/

  ULONG txTm_s ;         /* 32 bits and the most important field the client cares about. Transmit time-stamp seconds.*/
  ULONG txTm_f ;         /* 32 bits. Transmit time-stamp fraction of a second.*/

} ntp_packet ;           /* Total: 384 bits or 48 bytes. */


struct Library *SocketBase = NULL ;

struct Device *TimerBase = NULL ;
struct MsgPort *mp = NULL ;
struct timerequest *tr = NULL ;

struct LocaleBase *LocaleBase = NULL ;
struct Locale *locale = NULL ;

void error( char* msg )
{
  printf(msg) ;

  if (SocketBase != NULL)
  {
    CloseLibrary(SocketBase) ;
  }

  if (tr != NULL)
  {
    CloseDevice((struct IORequest *)tr) ;
    DeleteExtIO((struct IORequest *)tr);
  }

  if (mp != NULL)
  {
    DeletePort(mp) ;
  }

  exit(0) ; // Quit the process.
}

int skread(int sock_id, char *buff, int len)
{
  int msglen     = 0 ;
  int msgtodolen = len ;
  int msgcur     = 0 ;

  do
  {
    msglen = recv(sock_id, buff+msgcur, msgtodolen, 0) ;

    if (msglen <= 0)
    {
    }
    else
    {
      msgcur     += msglen ;
      msgtodolen -= msglen ;
    }
  } while ((msglen > 0) && (msgtodolen > 0)) ;

  return msgcur ;
}

int skwrite(int sock_id, char *buff, int len)
{
  int msglen     = 0 ;
  int msgtodolen = len ;
  int msgcur     = 0 ;

  do
  {
    msglen = send(sock_id, buff+msgcur, msgtodolen, 0) ;
    if (msglen > 0)
    {
      msgcur     += msglen ;
      msgtodolen -= msglen ;
    }
  } while ((msglen > 0) && (msgtodolen > 0)) ;

  return msgcur ;
}

#define LEAPYEAR(year)          (!((year) % 4) && (((year) % 100) || !((year) % 400)))
#define YEARSIZE(year)          (LEAPYEAR(year) ? 366 : 365)

const int _ytab[2][12] = {
  {31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31},
  {31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31}
} ;

struct tm
{
  int tm_sec ;
  int tm_min ;
  int tm_hour ;
  int tm_mday ;
  int tm_mon ;
  int tm_year ;
  int tm_wday ;
  int tm_yday ;
  int tm_isdst ;
} ;

void myctime(ULONG t, struct tm *lt)
{
  unsigned long dayno, dayclock ;
  int year = 1978 ;
  struct tm *tmbuf ;
  struct tm toto ;
  
  memset(&toto, 0, sizeof(toto)) ;
  tmbuf = &toto ;

  dayclock = (unsigned long)t % (24 * 60 * 60) ;
  dayno = (unsigned long)t / (24 * 60 * 60) ;

  tmbuf->tm_sec = dayclock % 60;
  tmbuf->tm_min = (dayclock % 3600) / 60;
  tmbuf->tm_hour = dayclock / 3600;
  tmbuf->tm_wday = (dayno + 4) % 7; // Day 0 was a thursday
  while (dayno >= (unsigned long) YEARSIZE(year)) {
    dayno -= YEARSIZE(year);
    year++;
  }
  tmbuf->tm_year = year - 1900 ;
  tmbuf->tm_yday = dayno;
  tmbuf->tm_mon = 0;
  while (dayno >= (unsigned long) _ytab[LEAPYEAR(year)][tmbuf->tm_mon]) {
    dayno -= _ytab[LEAPYEAR(year)][tmbuf->tm_mon];
    tmbuf->tm_mon++;
  }
  tmbuf->tm_mday = dayno + 1;
  tmbuf->tm_isdst = 0;
  //tmbuf->tm_gmtoff = 0;
  //tmbuf->tm_zone = "UTC";
  
  printf("%ld %ld %ld %02ld:%02ld:%02ld\n", tmbuf->tm_mday, 1+tmbuf->tm_mon, 1900+tmbuf->tm_year , tmbuf->tm_hour, tmbuf->tm_min, tmbuf->tm_sec) ;

  if (lt != NULL)
  {
    *lt = toto ;
  }
}


int main(int argc, char* argv[])
{
  int portno = 123 ; /* NTP UDP port number. */
  char* host_name = "pool.ntp.org" ; /* NTP server host-name. */
  struct sockaddr_in serv_addr ; /* Server address data structure. */
  struct hostent *server ;      /* Server data structure. */
  int sockfd, n ;
  ULONG txTm, t1, t2 ;
  struct tm loc_time ;
  ntp_packet packet ;
  long tz = 0 ;    /* timezone shift, workaround for SAS/C time() */
  long tzl = 0 ;   /* shift GMT, set by the Locale */
  int i ;
  int bSet = 0 ;

  for (i = 0 ; i < argc ; i++ )
  {
    if (stricmp(argv[i], "SET")==0)
    {
      bSet = 1 ;
    }
  }

  SocketBase = OpenLibrary("bsdsocket.library", 0) ;
  if (SocketBase == NULL)
  {
    printf("no TCP/IP stack, no BSDsocket.library\n") ;
    exit(0) ;
  }
  else
  {
    printf("%s %ld.%ld\n", SocketBase->lib_Node.ln_Name, SocketBase->lib_Version, SocketBase->lib_Revision ) ;
  }

  LocaleBase = (struct LocaleBase *)OpenLibrary("locale.library", 0) ;
  if (LocaleBase != NULL)
  {
    locale = OpenLocale(NULL) ;
    if (locale != NULL)
    {
      tzl = locale->loc_GMTOffset*60 ;
      CloseLocale(locale) ;
    }
    CloseLibrary((struct Library*)LocaleBase) ;
  }

  mp = CreatePort(NULL, 0) ;
  if (mp != NULL)
  {
    tr = (struct timerequest *)CreateExtIO(mp, sizeof(struct timerequest)) ;

    if (OpenDevice("timer.device", 
                   0,
                   (struct IORequest *)tr,
                   0) == 0)
    {
    
    }
  }

  TimerBase = tr->tr_node.io_Device ;

 

  memset( &packet, 0, sizeof( ntp_packet ) ) ;

  /* Set the first byte's bits to 00,011,011 for li = 0, vn = 3, and mode = 3. The rest will */
  /* be left set to zero. */
  *((char *) &packet + 0 ) = 0x1b ; /* Represents 27 in base 10 or 00011011 in base 2. */

  /* Create a UDP socket, convert the host-name to an IP address, set the port number, */
  /* connect to the server, send the packet, and then read in the return packet. */
  sockfd = socket( AF_INET, SOCK_DGRAM, IPPROTO_UDP ); /* Create a UDP socket. */

  if ( sockfd < 0 )
    error( "ERROR opening socket" );

  server = gethostbyname( host_name ); /* Convert URL to IP. */
  if ( server == NULL )
  {
    printf("ERROR, no such host %s\n", host_name) ;
    error("SORRY") ;
  }

  /* Zero out the server address structure. */
  memset(&serv_addr, 0, sizeof(serv_addr)) ;

  serv_addr.sin_family = AF_INET ;

  /* Copy the server's IP address to the server address structure. */
  bcopy( ( char* )server->h_addr, ( char* ) &serv_addr.sin_addr.s_addr, server->h_length );

  /* Convert the port number integer to network big-endian style and save it to the server  */
  /* address structure. */

  serv_addr.sin_port = htons( portno );

  /* Call up the server using its IP address and port number. */

  if ( connect( sockfd, ( struct sockaddr * ) &serv_addr, sizeof( serv_addr) ) < 0 )
    error( "ERROR connecting" );

  //printf("Send ntp packet to %s\n", host_name) ;
  /* Send it the NTP packet it wants. If n == -1, it failed. */

  n = skwrite( sockfd, ( char* ) &packet, sizeof( ntp_packet ) );

  if ( n < 0 )
    error( "ERROR writing to socket" ) ;

  //printf("Wait packet back\n\n") ;
  /* Wait and receive the packet back from the server. If n == -1, it failed. */

  n = skread( sockfd, ( char* ) &packet, sizeof( ntp_packet ) );

  if ( n < 0 )
    error( "ERROR reading from socket" );

  /* These two fields contain the time-stamp seconds as the packet left the NTP server. */
  /* The number of seconds correspond to the seconds passed since 1900. */
  /* ntohl() converts the bit/byte order from the network's to host's "endianness". */

  packet.txTm_s = ntohl( packet.txTm_s ); /* Time-stamp seconds. */
  packet.txTm_f = ntohl( packet.txTm_f ); /* Time-stamp fraction of a second. */

  /* Extract the 32 bits that represent the time-stamp seconds (since NTP epoch) from when the packet */
  /* left the server.                                                                                 */
  /* Subtract 70 years worth of seconds from the seconds since 1900.                                  */
  /* This leaves the seconds since Amiga epoch of 1978.                                               */
  /* (1900)------------------(1978)**************************************(Time Packet Left the Server)*/

  txTm = ( ULONG ) ( packet.txTm_s - NTP_TIMESTAMP_DELTA_1978 ) ;
  t1 = txTm ;

  myctime(t1, NULL) ;

  tr->tr_node.io_Command = TR_GETSYSTIME ;
  tr->tr_time.tv_secs  = 0 ;
  tr->tr_time.tv_micro = 0 ;
  DoIO((struct IORequest *)tr) ;
  t2 = tr->tr_time.tv_secs ;

  myctime(t2, NULL) ;
  printf ("Diff %ld s\n", t2-t1+tzl) ;

  if (bSet)
  {
    char buff[40] ;
    ULONG t ;
    t = t1 - tzl ;
    myctime(t, &loc_time) ;

    sprintf(buff, "date %02ld-%02ld-%04ld %02ld:%02ld:%02ld", loc_time.tm_mday, 1+loc_time.tm_mon, 1900+loc_time.tm_year , loc_time.tm_hour, loc_time.tm_min, loc_time.tm_sec) ;
    printf("%s\n", buff) ;

    tr->tr_node.io_Command = TR_SETSYSTIME ;
    tr->tr_time.tv_secs  = t ;
    tr->tr_time.tv_micro = 0 ;
    DoIO((struct IORequest *)tr) ;
  }

  if (tr != NULL)
  {
    CloseDevice((struct IORequest *)tr) ;
    DeleteExtIO((struct IORequest *)tr);
  }

  if (mp != NULL)
  {
    DeletePort(mp) ;
  }

  CloseLibrary(SocketBase) ;

  return 0;
}

Makefile SAS/C
Dans la user-startup ou la startup-sequence il devrait y avoir la définition des includes pour le SAS C à l'aide de la commande assign.
assign INCLUDE: dev:sc/include
ajouter
assign NETINCLUDE: dev:sdks/sdk_53.30/netinclude
Ce fameux netinclude se trouve dans SDK_Install\base.lha\Include\ du sdk_53.30.lha

Après avoir recopié le fichier source ntp.c dans un répertoire (nommé ntp aussi, il faut savoir ranger ses programmes :) ), créé les répertoires obj et bin puis édité un fichier makefile comme suit:

SRC=
OBJ=obj
BIN=bin

target: $(BIN)/ntp

$(OBJ)/ntp.o: $(SRC)/ntp.c
  sc INCLUDEDIR=NETINCLUDE: OBJNAME $(OBJ)/ntp.o $(SRC)/ntp.c

$(BIN)/ntp: $(OBJ)/ntp.o
  slink LIB:c.o,$(OBJ)/ntp.o TO $(BIN)/ntp LIB LIB:sc.lib NOICONS

De cette manière, le pré-compilateur ira chercher dans NETINCLUDE: puis dans INCLUDE: s'il ne trouve pas son include.
Et puis on ne perturbe pas ses includes préférés, puisqu'ils sont dans des répertoires distincts.
La première ligne signifie que l'on veut construire le programme ntp.
La commande sc compile le code source ntp.c avec les options qui vont bien.
La commande slink lie le petit segment de démarrage c.o avec les fonctions C standard de sc.lib pour générer le programme ntp.

Makefile gcc

SRC=..
OBJ=obj
BIN=bin

target: $(BIN)/ntp

$(OBJ)/ntp.o: $(SRC)/ntp.c
	gcc -noixemul -c -INETINCLUDE: -o $(OBJ)/ntp.o $(SRC)/ntp.c

$(BIN)/ntp: $(OBJ)/ntp.o
	gcc -noixemul -o $(BIN)/ntp $(OBJ)/ntp.o
	strip $(BIN)/ntp
Même principe que pour le makefile SAS/C mais avec gcc.

Essai
Voilà ce que donne le programme ntp.
On envoie une demande au serveur pool.ntp.org par le port 113, le serveur répond, on ajuste le temps et on compare avec la date de l'ordinateur.
Il a 4 secondes de retard.

bsdsocket.library 4.1
Send ntp packet to pool.ntp.org
Wait packet back

Server Time: Sat Jan 29 21:15:47 2022
Actual Time: Sat Jan 29 22:15:43 2022
Diff -4 s
option set, pour mettre à l'heure l'Amiga, avec l'heure réseau.
ntp set
bsdsocket.library 4.1
Send ntp packet to pool.ntp.org
Wait packet back

Server Time: Sat Jan 29 21:17:07 2022
Actual Time: Sat Jan 29 22:17:03 2022
Diff -4 s
date 29-01-2022 22:17:07

Conclusion
Voilà un petit utilitaire très utile pour des ordinateurs qui tournent dans des machines virtuelles, car l'horloge interne a tendance à dériver après plusieurs heures d'activité.