text: /****************************************************************************
   * netutils/ntpclient/ntpclient.c
   *
   *   Copyright (C) 2014, 2016, 2020 Gregory Nutt. All rights reserved.
   *   Author: Gregory Nutt <gnutt@nuttx.org>
   *
   * Redistribution and use in source and binary forms, with or without
   * modification, are permitted provided that the following conditions
   * are met:
   *
   * 1. Redistributions of source code must retain the above copyright
   *    notice, this list of conditions and the following disclaimer.
   * 2. Redistributions in binary form must reproduce the above copyright
   *    notice, this list of conditions and the following disclaimer in
   *    the documentation and/or other materials provided with the
   *    distribution.
   * 3. Neither the name NuttX nor the names of its contributors may be
   *    used to endorse or promote products derived from this software
   *    without specific prior written permission.
   *
   * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
   * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
   * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
   * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
   * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
   * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
   * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
   * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
   * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
   * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
   * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
   * POSSIBILITY OF SUCH DAMAGE.
   *
   ****************************************************************************/
  
  /****************************************************************************
   * Included Files
   ****************************************************************************/
  
  #include <nuttx/config.h>
  
  #include <stdbool.h>
  #include <stdint.h>
  
  #include <sys/socket.h>
  #include <sys/time.h>
  
  #include <stdio.h>
  #include <stdlib.h>
  #include <string.h>
  #include <time.h>
  #include <sched.h>
  #include <errno.h>
  #include <debug.h>
  
  #include <netinet/in.h>
  
  #ifdef CONFIG_LIBC_NETDB
  #  include <netdb.h>
  #  include <arpa/inet.h>
  #endif
  
  #include <nuttx/clock.h>
  
  #include "netutils/ntpclient.h"
  
  #include "ntpv3.h"
  
  /****************************************************************************
   * Pre-processor Definitions
   ****************************************************************************/
  
  /* Configuration ************************************************************/
  
  #ifndef CONFIG_HAVE_LONG_LONG
  #  error "64-bit integer support required for NTP client"
  #endif
  
  #if defined(CONFIG_LIBC_NETDB) && !defined(CONFIG_NETUTILS_NTPCLIENT_SERVER)
  #  error "CONFIG_NETUTILS_NTPCLIENT_SERVER must be provided"
  #endif
  
  #if !defined(CONFIG_LIBC_NETDB) && !defined(CONFIG_NETUTILS_NTPCLIENT_SERVERIP)
  #  error "CONFIG_NETUTILS_NTPCLIENT_SERVERIP must be provided"
  #endif
  
  #ifndef CONFIG_NETUTILS_NTPCLIENT_NUM_SAMPLES
  #  define CONFIG_NETUTILS_NTPCLIENT_NUM_SAMPLES 5
  #elif CONFIG_NETUTILS_NTPCLIENT_NUM_SAMPLES < 1
  #  error "NTP sample number below 1, invalid configuration"
  #endif
  
  #ifndef CONFIG_NETUTILS_NTPCLIENT_SERVER
  #  ifdef CONFIG_NETUTILS_NTPCLIENT_SERVERIP
  /* Old config support */
  #    warning "NTP server hostname not defined, using deprecated server IP address setting"
  #    define CONFIG_NETUTILS_NTPCLIENT_SERVER \
            inet_ntoa(CONFIG_NETUTILS_NTPCLIENT_SERVERIP)
  #  else
  #    error "NTP server hostname not defined"
  #  endif
  #endif
  
  /* NTP Time is seconds since 1900. Convert to Unix time which is seconds
   * since 1970
   */
  
  #define NTP2UNIX_TRANSLATION 2208988800u
  
  #define NTP_VERSION_V3       3
  #define NTP_VERSION_V4       4
  #define NTP_VERSION          NTP_VERSION_V4
  
  #define MAX_SERVER_SELECTION_RETRIES 3
  
  #ifndef ARRAY_SIZE
  #  define ARRAY_SIZE(x) (sizeof(x) / sizeof((x)[0]))
  #endif
  
  #ifndef STR
  #  define STR2(x) #x
  #  define STR(x) STR2(x)
  #endif
  
  /****************************************************************************
   * Private Types
   ****************************************************************************/
  
  /* This enumeration describes the state of the NTP daemon */
  
  enum ntpc_daemon_e
  {
    NTP_NOT_RUNNING = 0,
    NTP_STARTED,
    NTP_RUNNING,
    NTP_STOP_REQUESTED,
    NTP_STOPPED
  };
  
  /* This type describes the state of the NTP client daemon.  Only one
   * instance of the NTP daemon is permitted in this implementation.
   */
  
  struct ntpc_daemon_s
  {
    uint8_t state;       /* See enum ntpc_daemon_e */
    sem_t lock;          /* Used to protect the whole structure */
    sem_t sync;          /* Used to synchronize start and stop events */
    pid_t pid;           /* Task ID of the NTP daemon */
    sq_queue_t kod_list; /* KoD excluded server addresses */
    int family;          /* Allowed address family */
  };
  
  union ntp_addr_u
  {
    struct sockaddr sa;
  #ifdef CONFIG_NET_IPv4
    struct sockaddr_in in4;
  #endif
  #ifdef CONFIG_NET_IPv6
    struct sockaddr_in6 in6;
  #endif
    struct sockaddr_storage ss;
  };
  
  /* NTP offset. */
  
  struct ntp_sample_s
  {
    int64_t offset;
    int64_t delay;
    union ntp_addr_u srv_addr;
  } packet_struct;
  
  /* Server address list. */
  
  struct ntp_servers_s
  {
    union ntp_addr_u list[CONFIG_NETUTILS_NTPCLIENT_NUM_SAMPLES];
    size_t num;
    size_t pos;
    FAR char *hostlist_str;
    FAR char *hostlist_saveptr;
    FAR char *hostnext;
    FAR const char *ntp_servers;
  };
  
  /* KoD exclusion list. */
  
  struct ntp_kod_exclude_s
  {
    sq_entry_t node;
    union ntp_addr_u addr;
  };
  
  /****************************************************************************
   * Private Data
   ****************************************************************************/
  
  /* This type describes the state of the NTP client daemon.  Only one
   * instance of the NTP daemon is permitted in this implementation.  This
   * limitation is due only to this global data structure.
   */
  
  static struct ntpc_daemon_s g_ntpc_daemon =
  {
    NTP_NOT_RUNNING,
    SEM_INITIALIZER(1),
    SEM_INITIALIZER(0),
    -1,
    { NULL, NULL },
    AF_UNSPEC,         /* Default is both IPv4 and IPv6 */
  };
  
  static struct ntp_sample_s g_last_samples
      [CONFIG_NETUTILS_NTPCLIENT_NUM_SAMPLES];
  unsigned int g_last_nsamples = 0;
  
  /****************************************************************************
   * Private Functions
   ****************************************************************************/
  
  /****************************************************************************
   * Name: sample_cmp
   ****************************************************************************/
  
  static int sample_cmp(FAR const void *_a, FAR const void *_b)
  {
    FAR const struct ntp_sample_s *a = _a;
    FAR const struct ntp_sample_s *b = _b;
    int64_t diff = a->offset - b->offset;
  
    if (diff < 0)
      {
        return -1;
      }
    else if (diff > 0)
      {
        return 1;
      }
    else
      {
        return 0;
      }
  }
  
  /****************************************************************************
   * Name: int64abs
   ****************************************************************************/
  
  static inline int64_t int64abs(int64_t value)
  {
    return value >= 0 ? value : -value;
  }
  
  /****************************************************************************
   * Name: ntpc_get_compile_timestamp
   ****************************************************************************/
  
  static time_t ntpc_get_compile_timestamp(void)
  {
    struct tm tm;
    int year;
    int day;
    int month;
    bool unknown = true;
    time_t tim;
  #ifdef __DATE__
    const char *pmonth;
    const char *pyear;
    const char *pday;
  
    /* Compile date. Format: "MMM DD YYYY", where MMM is month in three letter
     * format, DD is day of month (left padded with space if less than ten) and
     * YYYY is year. "??? ?? ????" if unknown.
     */
  
    pmonth = __DATE__;
    pday = pmonth + 4;
    pyear = pmonth + 7;
  
    year = (pyear[0] - '0') * 1000
           + (pyear[1] - '0') * 100
           + (pyear[2] - '0') * 10
           + (pyear[3] - '0') * 1;
  
    day = (pday[1] - '0');
    if (pday[0] != ' ')
      {
        day += (pday[0] - '0') * 10;
      }
  
    unknown = false;
    switch (pmonth[0])
      {
      default:
        unknown = true;
        break;
      case 'J':
        if (pmonth[1] == 'a') /* Jan */
          month = 1;
        else if (pmonth[2] == 'n') /* Jun */
          month = 6;
        else /* Jul */
          month = 7;
        break;
      case 'F': /* Feb */
        month = 2;
        break;
      case 'M':
        if (pmonth[2] == 'r') /* Mar */
          month = 3;
        else /* May */
          month = 5;
        break;
      case 'A':
        if (pmonth[1] == 'p') /* Apr */
          month = 4;
        else /* Aug */
          month = 8;
        break;
      case 'S': /* Sep */
        month = 9;
        break;
      case 'O': /* Oct */
        month = 10;
        break;
      case 'N': /* Nov */
        month = 11;
        break;
      case 'D': /* Dec */
        month = 12;
        break;
      }
  #endif
  
    if (unknown)
      {
        month = 8;
        day = 18;
        year = 2015;
      }
  
    /* Convert date to timestamp. */
  
    memset(&tm, 0, sizeof(tm));
    tm.tm_hour = 0;
    tm.tm_min = 0;
    tm.tm_sec = 0;
    tm.tm_mday = day;
    tm.tm_mon = month - 1;
    tm.tm_year = year - 1900;
  
    tim = mktime(&tm);
  
    /* Reduce by one day to discount timezones. */
  
    tim -= 24 * 60 * 60;
  
    return tim;
  }
  
  /****************************************************************************
   * Name: ntpc_getuint32
   *
   * Description:
   *   Return the big-endian, 4-byte value in network (big-endian) order.
   *
   ****************************************************************************/
  
  static inline uint32_t ntpc_getuint32(FAR const uint8_t *ptr)
  {
    /* Network order is big-endian; host order is irrelevant */
  
    return (uint32_t)ptr[3] |          /* MS byte appears first in data stream */
           ((uint32_t)ptr[2] << 8) |
           ((uint32_t)ptr[1] << 16) |
           ((uint32_t)ptr[0] << 24);
  }
  
  /****************************************************************************
   * Name: ntpc_getuint64
   *
   * Description:
   *   Return the big-endian, 8-byte value in network (big-endian) order.
   *
   ****************************************************************************/
  
  static inline uint64_t ntpc_getuint64(FAR const uint8_t *ptr)
  {
    return ((uint64_t)ntpc_getuint32(ptr) << 32) | ntpc_getuint32(&ptr[4]);
  }
  
  /****************************************************************************
   * Name: ntpc_setuint32
   *
   * Description:
   *   Write 4-byte value to buffer in network (big-endian) order.
   *
   ****************************************************************************/
  
  static inline void ntpc_setuint32(FAR uint8_t *ptr, uint32_t value)
  {
    ptr[3] = (uint8_t)value;
    ptr[2] = (uint8_t)(value >> 8);
    ptr[1] = (uint8_t)(value >> 16);
    ptr[0] = (uint8_t)(value >> 24);
  }
  
  /****************************************************************************
   * Name: ntpc_setuint64
   *
   * Description:
   *   Write 8-byte value to buffer in network (big-endian) order.
   *
   ****************************************************************************/
  
  static inline void ntpc_setuint64(FAR uint8_t *ptr, uint64_t value)
  {
    ntpc_setuint32(ptr + 0, (uint32_t)(value >> 32));
    ntpc_setuint32(ptr + 4, (uint32_t)value);
  }
  
  /****************************************************************************
   * Name: ntp_secpart
   ****************************************************************************/
  
  static uint32_t ntp_secpart(uint64_t time)
  {
    /* NTP timestamps are represented as a 64-bit fixed-point number, in
     * seconds relative to 0000 UT on 1 January 1900.  The integer part is
     * in the first 32 bits and the fraction part in the last 32 bits, as
     * shown in the following diagram.
     *
     *    0                   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
     *   +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
     *   |                         Integer Part                          |
     *   +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
     *   |                         Fraction Part                         |
     *   +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
     */
  
    /* Get seconds part. */
  
    return time >> 32;
  }
  
  /****************************************************************************
   * Name: ntp_nsecpart
   ****************************************************************************/
  
  static uint32_t ntp_nsecpart(uint64_t time)
  {
    /* Get fraction part converted to nanoseconds. */
  
    return ((time & 0xffffffffu) * NSEC_PER_SEC) >> 32;
  }
  
  /****************************************************************************
   * Name: timespec2ntp
   *
   * Convert UNIX timespec timestamp to NTP 64-bit fixed point timestamp.
   *
   ****************************************************************************/
  
  static uint64_t timespec2ntp(FAR const struct timespec *ts)
  {
    uint64_t ntp_time;
  
    /* Set fraction part. */
  
    ntp_time = ((uint64_t)(ts->tv_nsec) << 32) / NSEC_PER_SEC;
  
    DEBUGASSERT((ntp_time >> 32) == 0);
  
    /* Set seconds part. */
  
    ntp_time += (uint64_t)(ts->tv_sec) << 32;
  
    return ntp_time;
  }
  
  /****************************************************************************
   * Name: ntp_gettime
   *
   * Get time in NTP epoch, stored in fixed point uint64_t format (upper 32-bit
   * seconds, lower 32-bit fraction)
   ****************************************************************************/
  
  static uint64_t ntp_localtime(void)
  {
    int err = errno;
    struct timespec currts;
  
    /* Get current clock in NTP epoch. */
  
    clock_gettime(CLOCK_REALTIME, &currts);
    currts.tv_sec += NTP2UNIX_TRANSLATION;
  
    /* Restore errno. */
  
    errno = err;
  
    return timespec2ntp(&currts);
  }
  
  /****************************************************************************
   * Name: ntpc_calculate_offset
   *
   * Description:
   *   Calculate NTP time offset and round-trip delay
   *
   ****************************************************************************/
  
  static void ntpc_calculate_offset(FAR int64_t *offset, FAR int64_t *delay,
                                    uint64_t local_xmittime,
                                    uint64_t local_recvtime,
                                    FAR const uint8_t *remote_recv,
                                    FAR const uint8_t *remote_xmit)
  {
    uint64_t remote_recvtime;
    uint64_t remote_xmittime;
  
    /* Two timestamps from server, when request was received, and response
     * send.
     */
  
    remote_recvtime = ntpc_getuint64(remote_recv);
    remote_xmittime = ntpc_getuint64(remote_xmit);
  
    /* Calculate offset of local time compared to remote time.
     * See: https://www.eecis.udel.edu/~mills/time.html
     *      http://nicolas.aimon.fr/2014/12/05/timesync/
     */
  
    *offset = (int64_t)((remote_recvtime / 2 - local_xmittime / 2) +
                       (remote_xmittime / 2 - local_recvtime / 2));
  
    /* Calculate roundtrip delay. */
  
    *delay = (local_recvtime - local_xmittime) -
            (remote_xmittime - remote_recvtime);
  }
  
  /****************************************************************************
   * Name: ntpc_settime
   *
   * Description:
   *   Given the NTP time offset, adjust the system time
   *
   ****************************************************************************/
  
  static void ntpc_settime(int64_t offset, FAR struct timespec *start_realtime,
                           FAR struct timespec *start_monotonic)
  {
    struct timespec tp;
    struct timespec curr_realtime;
  #ifdef CONFIG_CLOCK_MONOTONIC
    struct timespec curr_monotonic;
    int64_t diffms_real;
    int64_t diffms_mono;
    int64_t diff_diff_ms;
  #endif
  
    /* Get the system times */
  
    clock_gettime(CLOCK_REALTIME, &curr_realtime);
  
  #ifdef CONFIG_CLOCK_MONOTONIC
    clock_gettime(CLOCK_MONOTONIC, &curr_monotonic);
  
    /* Check differences between monotonic and realtime. */
  
    diffms_real = curr_realtime.tv_sec - start_realtime->tv_sec;
    diffms_real *= 1000;
    diffms_real += (int64_t)(curr_realtime.tv_nsec -
                             start_realtime->tv_nsec) / (1000 * 1000);
  
    diffms_mono = curr_monotonic.tv_sec - start_monotonic->tv_sec;
    diffms_mono *= 1000;
    diffms_mono += (int64_t)(curr_monotonic.tv_nsec -
                             start_monotonic->tv_nsec) / (1000 * 1000);
  
    /* Detect if real-time has been altered by other task. */
  
    diff_diff_ms = diffms_real - diffms_mono;
    if (diff_diff_ms < 0)
      {
        diff_diff_ms = -diff_diff_ms;
      }
  
    if (diff_diff_ms >= 1000)
      {
        nwarn("System time altered by other task by %ju msecs, "
              "do not apply offset.\n", (intmax_t)diff_diff_ms);
  
        return;
      }
  #else
    UNUSED(start_monotonic);
  #endif
  
    /* Apply offset */
  
    tp = curr_realtime;
    tp.tv_sec  += ntp_secpart(offset);
    tp.tv_nsec += ntp_nsecpart(offset);
    while (tp.tv_nsec >= NSEC_PER_SEC)
      {
        tp.tv_nsec -= NSEC_PER_SEC;
        tp.tv_sec++;
      }
  
    /* Set the system time */
  
    clock_settime(CLOCK_REALTIME, &tp);
  
    ninfo("Set time to %ju.%03ld seconds (offset: %s%lu.%03lu).\n",
          (intmax_t)tp.tv_sec, tp.tv_nsec / NSEC_PER_MSEC,
          offset < 0 ? "-" : "",
          (unsigned long)ntp_secpart(int64abs(offset)),
          ntp_nsecpart(int64abs(offset)) / NSEC_PER_MSEC);
  }
  
  /****************************************************************************
   * Name: ntp_address_in_kod_list
   *
   * Description: Check if address is in KoD KoD exclusion list.
   *
   ****************************************************************************/
  
  static bool ntp_address_in_kod_list(FAR const union ntp_addr_u *server_addr)
  {
    FAR struct ntp_kod_exclude_s *entry;
  
    entry = (FAR void *)sq_peek(&g_ntpc_daemon.kod_list);
    while (entry)
      {
        if (memcmp(&entry->addr, server_addr, sizeof(*server_addr)) == 0)
          {
            return true;
          }
  
        entry = (FAR void *)sq_next(&entry->node);
      }
  
    return false;
  }
  
  /****************************************************************************
   * Name: ntp_is_kiss_of_death
   *
   * Description: Check if this is KoD response from the server. If it is,
   * add server to KoD exclusion list and return 'true'.
   *
   ****************************************************************************/
  
  static bool ntp_is_kiss_of_death(FAR const struct ntp_datagram_s *recv,
                                   FAR const union ntp_addr_u *server_addr)
  {
    /* KoD only specified for v4. */
  
    if (GETVN(recv->lvm) != NTP_VERSION_V4)
      {
        if (recv->stratum == 0)
          {
            /* Stratum 0 is unspecified on v3, so ignore packet. */
  
            return true;
          }
        else
          {
            return false;
          }
      }
  
    /* KoD message if stratum == 0. */
  
    if (recv->stratum != 0)
      {
        return false;
      }
  
    /* KoD message received. */
  
    /* Check if we need to add server to access exclusion list. */
  
    if (strncmp((char *)recv->refid, "DENY", 4) == 0 ||
        strncmp((char *)recv->refid, "RSTR", 4) == 0 ||
        strncmp((char *)recv->refid, "RATE", 4) == 0)
      {
        struct ntp_kod_exclude_s *entry;
  
        entry = calloc(1, sizeof(*entry));
        if (entry)
          {
            entry->addr = *server_addr;
            sq_addlast(&entry->node, &g_ntpc_daemon.kod_list);
          }
      }
  
    return true;
  }
  
  /****************************************************************************
   * Name: ntpc_verify_recvd_ntp_datagram
   ****************************************************************************/
  
  static bool ntpc_verify_recvd_ntp_datagram(
                  FAR const struct ntp_datagram_s *xmit,
                  FAR const struct ntp_datagram_s *recv,
                  size_t nbytes,
                  FAR const union ntp_addr_u *xmitaddr,
                  FAR const union ntp_addr_u *recvaddr,
                  size_t recvaddrlen)
  {
    time_t buildtime;
    time_t seconds;
  
    if (recvaddr->sa.sa_family != xmitaddr->sa.sa_family)
      {
        ninfo("wrong address family\n");
  
        return false;
      }
  
  #ifdef CONFIG_NET_IPv4
    if (recvaddr->sa.sa_family == AF_INET)
      {
        if (recvaddrlen != sizeof(struct sockaddr_in) ||
            xmitaddr->in4.sin_addr.s_addr != recvaddr->in4.sin_addr.s_addr ||
            xmitaddr->in4.sin_port != recvaddr->in4.sin_port)
          {
            ninfo("response from wrong peer\n");
  
            return false;
          }
      }
  #endif
  
  #ifdef CONFIG_NET_IPv6
    if (recvaddr->sa.sa_family == AF_INET6)
      {
        if (recvaddrlen != sizeof(struct sockaddr_in6) ||
            memcmp(&xmitaddr->in6.sin6_addr,
                   &recvaddr->in6.sin6_addr, sizeof(struct in6_addr)) != 0 ||
            xmitaddr->in6.sin6_port != recvaddr->in6.sin6_port)
          {
            ninfo("response from wrong peer\n");
  
            return false;
          }
      }
  #endif /* CONFIG_NET_IPv6 */
  
    if (nbytes < NTP_DATAGRAM_MINSIZE)
      {
        /* Too short. */
  
        ninfo("too short response\n");
  
        return false;
      }
  
    if (GETVN(recv->lvm) != NTP_VERSION_V3 &&
        GETVN(recv->lvm) != NTP_VERSION_V4)
      {
        /* Wrong version. */
  
        ninfo("wrong version: %d\n", GETVN(recv->lvm));
  
        return false;
      }
  
    if (GETMODE(recv->lvm) != 4)
      {
        /* Response not in server mode. */
  
        ninfo("wrong mode: %d\n", GETMODE(recv->lvm));
  
        return false;
      }
  
    if (ntp_is_kiss_of_death(recv, xmitaddr))
      {
        /* KoD, Kiss-o'-Death. Ignore response. */
  
        ninfo("kiss-of-death response\n");
  
        return false;
      }
  
    if (GETLI(recv->lvm) == 3)
      {
        /* Clock not synchronized. */
  
        ninfo("LI: not synchronized\n");
  
        return false;
      }
  
    if (memcmp(recv->origtimestamp, xmit->xmittimestamp, 8) != 0)
      {
        /* "The Originate Timestamp in the server reply should match the
         * Transmit Timestamp used in the client request."
         */
  
        ninfo("xmittimestamp mismatch\n");
  
        return false;
      }
  
    if (ntpc_getuint32(recv->reftimestamp) == 0)
      {
        /* Invalid timestamp. */
  
        ninfo("invalid reftimestamp, 0x%08lx\n",
              (unsigned long)ntpc_getuint32(recv->reftimestamp));
  
        return false;
      }
  
    if (ntpc_getuint32(recv->recvtimestamp) == 0)
      {
        /* Invalid timestamp. */
  
        ninfo("invalid recvtimestamp, 0x%08lx\n",
              (unsigned long)ntpc_getuint32(recv->recvtimestamp));
  
        return false;
      }
  
    if (ntpc_getuint32(recv->xmittimestamp) == 0)
      {
        /* Invalid timestamp. */
  
        ninfo("invalid xmittimestamp, 0x%08lx\n",
              (unsigned long)ntpc_getuint32(recv->xmittimestamp));
  
        return false;
      }
  
    if ((int64_t)(ntpc_getuint64(recv->xmittimestamp) -
                  ntpc_getuint64(recv->recvtimestamp)) < 0)
      {
        /* Remote received our request after sending response? */
  
        ninfo("invalid xmittimestamp & recvtimestamp pair\n");
  
        return false;
      }
  
    buildtime = ntpc_get_compile_timestamp();
    seconds = ntpc_getuint32(recv->recvtimestamp);
    if (seconds > NTP2UNIX_TRANSLATION)
      {
        seconds -= NTP2UNIX_TRANSLATION;
      }
  
    if (seconds < buildtime)
      {
        /* Invalid timestamp. */
  
        ninfo("invalid recvtimestamp, 0x%08lx\n",
              (unsigned long)ntpc_getuint32(recv->recvtimestamp));
  
        return false;
      }
  
    return true;
  }
  
  /****************************************************************************
   * Name: ntpc_create_dgram_socket
   ****************************************************************************/
  
  static int ntpc_create_dgram_socket(int domain)
  {
    struct timeval tv;
    int ret;
    int sd;
    int err;
  
    /* Create a datagram socket  */
  
    sd = socket(domain, SOCK_DGRAM, 0);
    if (sd < 0)
      {
        err = errno;
        nerr("ERROR: socket failed: %d\n", err);
        errno = err;
        return sd;
      }
  
    /* Setup a send timeout on the socket */
  
    tv.tv_sec  = 5;
    tv.tv_usec = 0;
  
    ret = setsockopt(sd, SOL_SOCKET, SO_SNDTIMEO, &tv, sizeof(struct timeval));
    if (ret < 0)
      {
        err = errno;
        nerr("ERROR: setsockopt(SO_SNDTIMEO) failed: %d\n", errno);
        goto err_close;
      }
  
    /* Setup a receive timeout on the socket */
  
    tv.tv_sec  = 5;
    tv.tv_usec = 0;
  
    ret = setsockopt(sd, SOL_SOCKET, SO_RCVTIMEO, &tv, sizeof(struct timeval));
    if (ret < 0)
      {
        err = errno;
        nerr("ERROR: setsockopt(SO_RCVTIMEO) failed: %d\n", errno);
        goto err_close;
      }
  
    return sd;
  
  err_close:
    close(sd);
    errno = err;
    return ret;
  }
  
  /****************************************************************************
   * Name: ntp_gethostip_multi
   ****************************************************************************/
  
  static int ntp_gethostip_multi(FAR const char *hostname,
                                 FAR union ntp_addr_u *ipaddr, size_t nipaddr)
  {
    struct addrinfo hints;
    FAR struct addrinfo *info;
    FAR struct addrinfo *next;
    int ret;
  
    memset(&hints, 0, sizeof(struct addrinfo));
    hints.ai_family   = g_ntpc_daemon.family;
    hints.ai_socktype = SOCK_DGRAM;
    hints.ai_protocol = IPPROTO_UDP;
    hints.ai_flags    = AI_NUMERICSERV;
  
    ret = getaddrinfo(hostname, STR(CONFIG_NETUTILS_NTPCLIENT_PORTNO),
                      &hints, &info);
    if (ret != OK)
      {
        nerr("ERROR: getaddrinfo(%s): %d: %s\n", hostname,
             ret, gai_strerror(ret));
        return ERROR;
      }
  
    ret = 0;
    for (next = info; next != NULL; next = next->ai_next)
      {
        if (ret >= nipaddr)
          {
            break;
          }
  
        memcpy(ipaddr, next->ai_addr, next->ai_addrlen);
        ret++;
        ipaddr++;
      }
  
    freeaddrinfo(info);
    return ret;
  }
  
  /****************************************************************************
   * Name: ntp_get_next_hostip
   ****************************************************************************/
  
  static int ntp_get_next_hostip(FAR struct ntp_servers_s *srvs,
                                 FAR union ntp_addr_u *addr)
  {
    int ret;
  
    if (srvs->pos >= srvs->num)
      {
        FAR char *hostname;
  
        srvs->pos = 0;
        srvs->num = 0;
  
        /* Get next hostname. */
  
        if (srvs->hostnext == NULL)
          {
            if (srvs->hostlist_str)
              {
                free(srvs->hostlist_str);
                srvs->hostlist_str = NULL;
              }
  
            /* Allocate hostname list buffer */
  
            srvs->hostlist_str = strdup(srvs->ntp_servers);
            if (!srvs->hostlist_str)
              {
                return ERROR;
              }
  
            srvs->hostlist_saveptr = NULL;
  #ifndef __clang_analyzer__ /* Silence false 'possible memory leak'. */
            srvs->hostnext =
                strtok_r(srvs->hostlist_str, ";", &srvs->hostlist_saveptr);
  #endif
          }
  
        hostname = srvs->hostnext;
        srvs->hostnext = strtok_r(NULL, ";", &srvs->hostlist_saveptr);
  
        if (!hostname)
          {
            /* Invalid configuration. */
  
            errno = EINVAL;
            return ERROR;
          }
  
        /* Refresh DNS for new IP-addresses. */
  
        ret = ntp_gethostip_multi(hostname, srvs->list,
                                  ARRAY_SIZE(srvs->list));
        if (ret <= 0)
          {
            return ERROR;
          }
  
        srvs->num = ret;
      }
  
    *addr = srvs->list[srvs->pos++];
  
    return OK;
  }
  
  /****************************************************************************
   * Name: ntpc_get_ntp_sample
   ****************************************************************************/
  
  static int ntpc_get_ntp_sample(FAR struct ntp_servers_s *srvs,
                                 FAR struct ntp_sample_s *samples,
                                 int curr_idx)
  {
    FAR struct ntp_sample_s *sample = &samples[curr_idx];
    uint64_t xmit_time, recv_time;
    union ntp_addr_u server;
    union ntp_addr_u recvaddr;
    struct ntp_datagram_s xmit;
    struct ntp_datagram_s recv;
    socklen_t socklen;
    ssize_t nbytes;
    int errval;
    int retry = 0;
    int nsamples = curr_idx;
    bool addr_ok;
    int ret;
    int sd = -1;
    int i;
  
    /* Setup an ntp_addr_u with information about the server we are
     * going to ask the time from.
     */
  
    memset(&server, 0, sizeof(server));
  
    do
      {
        addr_ok = true;
  
        ret = ntp_get_next_hostip(srvs, &server);
        if (ret < 0)
          {
            errval = errno;
  
            nerr("ERROR: ntp_get_next_hostip() failed: %d\n", errval);
  
            goto sock_error;
          }
  
        /* Make sure that server not in exclusion list. */
  
        if (ntp_address_in_kod_list(&server))
          {
            if (retry < MAX_SERVER_SELECTION_RETRIES)
              {
                ninfo("on KoD list. retry DNS.\n");
  
                retry++;
                addr_ok = false;
                continue;
              }
            else
              {
                errval = -EALREADY;
                goto sock_error;
              }
          }
  
        /* Make sure that this sample is from new server. */
  
        for (i = 0; i < nsamples; i++)
          {
            if (memcmp(&server, &samples[i].srv_addr, sizeof(server)) == 0)
              {
                /* Already have sample from this server, retry DNS. */
  
                ninfo("retry DNS\n");
  
                if (retry < MAX_SERVER_SELECTION_RETRIES)
                  {
                    retry++;
                    addr_ok = false;
                    break;
                  }
                else
                  {
                    /* Accept same server if cannot get DNS for other server. */
  
                    break;
                  }
              }
          }
      }
    while (!addr_ok);
  
    /* Open socket. */
  
    sd = ntpc_create_dgram_socket(server.sa.sa_family);
    if (sd < 0)
      {
        errval = errno;
  
        nerr("ERROR: ntpc_create_dgram_socket() failed: %d\n", errval);
  
        goto sock_error;
      }
  
    /* Format the transmit datagram */
  
    memset(&xmit, 0, sizeof(xmit));
    xmit.lvm = MKLVM(0, NTP_VERSION, 3);
  
    ninfo("Sending a NTPv%d packet\n", NTP_VERSION);
  
    xmit_time = ntp_localtime();
    ntpc_setuint64(xmit.xmittimestamp, xmit_time);
  
    socklen = (server.sa.sa_family == AF_INET) ? sizeof(struct sockaddr_in)
                                               : sizeof(struct sockaddr_in6);
    ret = sendto(sd, &xmit, sizeof(struct ntp_datagram_s),
                 0, &server.sa, socklen);
    if (ret < 0)
      {
        errval = errno;
  
        nerr("ERROR: sendto() failed: %d\n", errval);
  
        goto sock_error;
      }
  
    /* Attempt to receive a packet (with a timeout that was set up via
     * setsockopt() above)
     */
  
    nbytes = recvfrom(sd, (void *)&recv, sizeof(struct ntp_datagram_s),
                      0, &recvaddr.sa, &socklen);
    recv_time = ntp_localtime();
  
    /* Check if the received message was long enough to be a valid NTP
     * datagram.
     */
  
    if (nbytes > 0 && ntpc_verify_recvd_ntp_datagram(
                            &xmit, &recv, nbytes, &server, &recvaddr, socklen))
      {
        close(sd);
        sd = -1;
  
        ninfo("Calculate offset\n");
  
        memset(sample, 0, sizeof(struct ntp_sample_s));
  
        sample->srv_addr = server;
  
        ntpc_calculate_offset(&sample->offset, &sample->delay,
                              xmit_time, recv_time, recv.recvtimestamp,
                              recv.xmittimestamp);
  
        return OK;
      }
    else
      {
        /* Check for errors.  Short datagrams are handled as error. */
  
        errval = errno;
  
        if (nbytes >= 0)
          {
            errval = EMSGSIZE;
          }
        else
          {
            nerr("ERROR: recvfrom() failed: %d\n", errval);
          }
  
        goto sock_error;
      }
  
  sock_error:
    if (sd >= 0)
      {
        close(sd);
        sd = -1;
      }
  
    errno = errval;
    return ERROR;
  }
  
  /****************************************************************************
   * Name: ntpc_daemon
   *
   * Description:
   *   This the the NTP client daemon.  This is a *very* minimal
   *   implementation! An NTP request is and the system clock is set when the
   *   response is received
   *
   ****************************************************************************/
  
  static int ntpc_daemon(int argc, FAR char **argv)
  {
    struct ntp_sample_s samples[CONFIG_NETUTILS_NTPCLIENT_NUM_SAMPLES];
    struct ntp_servers_s srvs;
    int exitcode = EXIT_SUCCESS;
    int retries = 0;
    int nsamples;
    int ret;
  
    /* We must always be invoked with a list of servers. */
  
    DEBUGASSERT(argc > 1 && argv[1] != NULL && *argv[1] != '\0');
  
    memset(&srvs, 0, sizeof(srvs));
  
    /* Indicate that we have started */
  
    g_ntpc_daemon.state = NTP_RUNNING;
    sem_post(&g_ntpc_daemon.sync);
  
    /* Here we do the communication with the NTP server. We collect set of
     * NTP samples (hopefully from different servers when using DNS) and
     * select median time-offset of samples. This is to filter out
     * misconfigured server giving wrong timestamps.
     *
     * NOTE that the scheduler is locked whenever this loop runs.  That
     * assures both:  (1) that there are no asynchronous stop requests and
     * (2) that we are not suspended while in critical moments when we about
     * to set the new time.  This sounds harsh, but this function is suspended
     * most of the time either: (1) sending a datagram, (2) receiving a
     * datagram, or (3) waiting for the next poll cycle.
     *
     * TODO: The first datagram that is sent is usually lost.  That is because
     * the MAC address of the NTP server is not in the ARP table.  This is
     * particularly bad here because the request will not be sent again until
     * the long delay expires leaving the system with bad time for a long time
     * initially.  Solutions:
     *
     * 1. Fix send logic so that it assures that the ARP request has been
     *    sent and the entry is in the ARP table before sending the packet
     *    (best).
     * 2. Add some ad hoc logic here so that there is no delay until at least
     *    one good time is received.
     */
  
    sched_lock();
    while (g_ntpc_daemon.state != NTP_STOP_REQUESTED)
      {
        struct timespec start_realtime, start_monotonic;
        int errval = 0;
        int i;
  
        free(srvs.hostlist_str);
        memset(&srvs, 0, sizeof(srvs));
        srvs.ntp_servers = argv[1];
  
        memset(samples, 0, sizeof(samples));
  
        clock_gettime(CLOCK_REALTIME, &start_realtime);
  #ifdef CONFIG_CLOCK_MONOTONIC
        clock_gettime(CLOCK_MONOTONIC, &start_monotonic);
  #endif
  
        /* Collect samples. */
  
        nsamples = 0;
        for (i = 0; i < CONFIG_NETUTILS_NTPCLIENT_NUM_SAMPLES; i++)
          {
            /* Get next sample. */
  
            ret = ntpc_get_ntp_sample(&srvs, samples, nsamples);
            if (ret < 0)
              {
                errval = errno;
              }
            else
              {
                ++nsamples;
              }
          }
  
        /* Analyse samples. */
  
        if (nsamples > 0)
          {
            int64_t offset;
  
            /* Select median offset of samples. */
  
            qsort(samples, nsamples, sizeof(*samples), sample_cmp);
  
            for (i = 0; i < nsamples; i++)
              {
                ninfo("NTP sample[%d]: offset: %s%lu.%03lu sec, "
                      "round-trip delay: %s%lu.%03lu sec\n",
                      i,
                      samples[i].offset < 0 ? "-" : "",
                      (unsigned long)ntp_secpart(int64abs(samples[i].offset)),
                      ntp_nsecpart(int64abs(samples[i].offset))
                        / NSEC_PER_MSEC,
                      samples[i].delay < 0 ? "-" : "",
                      (unsigned long)ntp_secpart(int64abs(samples[i].delay)),
                      ntp_nsecpart(int64abs(samples[i].delay))
                        / NSEC_PER_MSEC);
              }
  
            if ((nsamples % 2) == 1)
              {
                offset = samples[nsamples / 2].offset;
              }
            else
              {
                int64_t offset1 = samples[nsamples / 2].offset;
                int64_t offset2 = samples[nsamples / 2 - 1].offset;
  
                /* Average of two middle offsets. */
  
                if (offset1 > 0 && offset2 > 0)
                  {
                    offset = ((uint64_t)offset1 + (uint64_t)offset2) / 2;
                  }
                else if (offset1 < 0 && offset2 < 0)
                  {
                    offset1 = -offset1;
                    offset2 = -offset2;
  
                    offset = ((uint64_t)offset1 + (uint64_t)offset2) / 2;
  
                    offset = -offset;
                  }
                else
                  {
                    offset = (offset1 + offset2) / 2;
                  }
              }
  
            /* Adjust system time. */
  
            ntpc_settime(offset, &start_realtime, &start_monotonic);
  
            /* Save samples for ntpc_status() */
  
            sem_wait(&g_ntpc_daemon.lock);
            g_last_nsamples = nsamples;
            memcpy(&g_last_samples, samples, nsamples * sizeof(*samples));
            sem_post(&g_ntpc_daemon.lock);
  
  #ifndef CONFIG_NETUTILS_NTPCLIENT_STAY_ON
            /* Configured to exit at success. */
  
            exitcode = EXIT_SUCCESS;
            break;
  
  #else
  
            /* A full implementation of an NTP client would require much
             * more.  I think we can skip most of that here.
             */
  
            if (g_ntpc_daemon.state == NTP_RUNNING)
              {
                ninfo("Waiting for %d seconds\n",
                      CONFIG_NETUTILS_NTPCLIENT_POLLDELAYSEC);
  
                sleep(CONFIG_NETUTILS_NTPCLIENT_POLLDELAYSEC);
                retries = 0;
              }
  
            continue;
  #endif
          }
  
        /* Exceeded maximum retries? */
  
        if (retries++ >= CONFIG_NETUTILS_NTPCLIENT_RETRIES)
          {
            nerr("ERROR: too many retries: %d\n", retries - 1);
            exitcode = EXIT_FAILURE;
            break;
          }
  
        /* Is this error a signal? If not, sleep before retry. */
  
        if (errval != EINTR)
          {
            sleep(1);
          }
  
        /* Keep retrying. */
      }
  
    /* The NTP client is terminating */
  
    sched_unlock();
  
    g_ntpc_daemon.state = NTP_STOPPED;
    sem_post(&g_ntpc_daemon.sync);
    free(srvs.hostlist_str);
    return exitcode;
  }
  
  /****************************************************************************
   * Public Functions
   ****************************************************************************/
  
  /****************************************************************************
   * Name: ntpc_dualstack_family()
   *
   * Description:
   *   Set the protocol family used (AF_INET, AF_INET6 or AF_UNSPEC)
   *
   ****************************************************************************/
  
  void ntpc_dualstack_family(int family)
  {
    sem_wait(&g_ntpc_daemon.lock);
    g_ntpc_daemon.family = family;
    sem_post(&g_ntpc_daemon.lock);
  }
  
  /****************************************************************************
   * Name: ntpc_start_with_list
   *
   * Description:
   *   Start the NTP daemon
   *
   * Returned Value:
   *   On success, the non-negative task ID of the NTPC daemon is returned;
   *   On failure, a negated errno value is returned.
   *
   ****************************************************************************/
  
  int ntpc_start_with_list(FAR const char *ntp_server_list)
  {
    FAR char *task_argv[] =
      {
        (FAR char *)ntp_server_list,
        NULL
      };
  
    /* Is the NTP in a non-running state? */
  
    sem_wait(&g_ntpc_daemon.lock);
    if (g_ntpc_daemon.state == NTP_NOT_RUNNING ||
        g_ntpc_daemon.state == NTP_STOPPED)
      {
        /* Start the NTP daemon */
  
        g_ntpc_daemon.state = NTP_STARTED;
        g_ntpc_daemon.pid =
          task_create("NTP daemon", CONFIG_NETUTILS_NTPCLIENT_SERVERPRIO,
                      CONFIG_NETUTILS_NTPCLIENT_STACKSIZE, ntpc_daemon,
                      task_argv);
  
        /* Handle failures to start the NTP daemon */
  
        if (g_ntpc_daemon.pid < 0)
          {
            int errval = errno;
            DEBUGASSERT(errval > 0);
  
            g_ntpc_daemon.state = NTP_STOPPED;
            nerr("ERROR: Failed to start the NTP daemon: %d\n", errval);
            sem_post(&g_ntpc_daemon.lock);
            return -errval;
          }
  
        /* Wait for any daemon state change */
  
        do
          {
            sem_wait(&g_ntpc_daemon.sync);
          }
        while (g_ntpc_daemon.state == NTP_STARTED);
      }
  
    sem_post(&g_ntpc_daemon.lock);
    return g_ntpc_daemon.pid;
  }
  
  /****************************************************************************
   * Name: ntpc_start
   *
   * Description:
   *   Start the NTP daemon
   *
   * Returned Value:
   *   On success, the non-negative task ID of the NTPC daemon is returned;
   *   On failure, a negated errno value is returned.
   *
   ****************************************************************************/
  
  int ntpc_start(void)
  {
    return ntpc_start_with_list(CONFIG_NETUTILS_NTPCLIENT_SERVER);
  }
  
  /****************************************************************************
   * Name: ntpc_stop
   *
   * Description:
   *   Stop the NTP daemon
   *
   * Returned Value:
   *   Zero on success; a negated errno value on failure.  The current
   *   implementation only returns success.
   *
   ****************************************************************************/
  
  int ntpc_stop(void)
  {
    int ret;
  
    /* Is the NTP in a running state? */
  
    sem_wait(&g_ntpc_daemon.lock);
    if (g_ntpc_daemon.state == NTP_STARTED ||
        g_ntpc_daemon.state == NTP_RUNNING)
      {
        /* Yes.. request that the daemon stop. */
  
        g_ntpc_daemon.state = NTP_STOP_REQUESTED;
  
        /* Wait for any daemon state change */
  
        do
          {
            /* Signal the NTP client */
  
            ret = kill(g_ntpc_daemon.pid,
                       CONFIG_NETUTILS_NTPCLIENT_SIGWAKEUP);
  
            if (ret < 0)
              {
                nerr("ERROR: kill pid %d failed: %d\n",
                     g_ntpc_daemon.pid, errno);
                break;
              }
  
            /* Wait for the NTP client to respond to the stop request */
  
            sem_wait(&g_ntpc_daemon.sync);
          }
        while (g_ntpc_daemon.state == NTP_STOP_REQUESTED);
      }
  
    sem_post(&g_ntpc_daemon.lock);
    return OK;
  }
  
  int ntpc_status(struct ntpc_status_s *statusp)
  {
    unsigned int i;
  
    sem_wait(&g_ntpc_daemon.lock);
    statusp->nsamples = g_last_nsamples;
    for (i = 0; i < g_last_nsamples; i++)
      {
        statusp->samples[i].offset = g_last_samples[i].offset;
        statusp->samples[i].delay = g_last_samples[i].delay;
        statusp->samples[i]._srv_addr_store = g_last_samples[i].srv_addr.ss;
        statusp->samples[i].srv_addr = (FAR const struct sockaddr *)
                                       &statusp->samples[i]._srv_addr_store;
      }
  
    sem_post(&g_ntpc_daemon.lock);
    return OK;
  }
  