nuttx-apps/netutils/ntpclient/ntpclient.c
Xiang Xiao 857158451b Unify the void cast usage
1.Remove void cast for function because many place ignore the returned value witout cast
2.Replace void cast for variable with UNUSED macro

Change-Id: Ie644129a563244a6397036789c4c3ea83c4e9b09
Signed-off-by: Xiang Xiao <xiaoxiang@xiaomi.com>
2020-01-02 23:21:01 +08:00

629 lines
18 KiB
C

/****************************************************************************
* netutils/ntpclient/ntpclient.c
*
* Copyright (C) 2014, 2016 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 <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 "netutils/ntpclient.h"
#include "ntpv3.h"
/****************************************************************************
* Pre-processor Definitions
****************************************************************************/
/* Configuration ************************************************************/
#if defined(CONFIG_LIBC_NETDB) && !defined(CONFIG_NETUTILS_NTPCLIENT_SERVER)
# error CONFIG_NETUTILS_NTPCLIENT_SERVER my be provided
#endif
#if !defined(CONFIG_LIBC_NETDB) && !defined(CONFIG_NETUTILS_NTPCLIENT_SERVERIP)
# error CONFIG_NETUTILS_NTPCLIENT_SERVERIP my be provided
#endif
/* NTP Time is seconds since 1900. Convert to Unix time which is seconds
* since 1970
*/
#define NTP2UNIX_TRANLSLATION 2208988800u
#define NTP_VERSION 3
/****************************************************************************
* 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
{
volatile uint8_t state; /* See enum ntpc_daemon_e */
sem_t interlock; /* Used to synchronize start and stop events */
pid_t pid; /* Task ID of the NTP daemon */
};
/****************************************************************************
* 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;
/****************************************************************************
* Private Functions
****************************************************************************/
/****************************************************************************
* Name: ntpc_getuint32
*
* Description:
* Return the big-endian, 4-byte value in network (big-endian) order.
*
****************************************************************************/
static inline uint32_t ntpc_getuint32(FAR 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_settime
*
* Description:
* Given the NTP time in seconds, set the system time
*
****************************************************************************/
static void ntpc_settime(FAR uint8_t *timestamp)
{
struct timespec tp;
time_t seconds;
uint32_t frac;
uint32_t nsec;
#ifdef CONFIG_HAVE_LONG_LONG
uint64_t tmp;
#else
uint32_t a16;
uint32_t b0;
uint32_t t32;
uint32_t t16;
uint32_t t0;
#endif
/* 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 |
* +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
*/
seconds = ntpc_getuint32(timestamp);
/* Translate seconds to account for the difference in the origin time */
if (seconds > NTP2UNIX_TRANLSLATION)
{
seconds -= NTP2UNIX_TRANLSLATION;
}
/* Conversion of the fractional part to nanoseconds:
*
* NSec = (f * 1,000,000,000) / 4,294,967,296
* = (f * (5**9 * 2**9) / (2**32)
* = (f * 5**9) / (2**23)
* = (f * 1,953,125) / 8,388,608
*/
frac = ntpc_getuint32(timestamp + 4);
#ifdef CONFIG_HAVE_LONG_LONG
/* if we have 64-bit long long values, then the computation is easy */
tmp = ((uint64_t)frac * 1953125) >> 23;
nsec = (uint32_t)tmp;
#else
/* If we don't have 64 bit integer types, then the calculation is a little
* more complex:
*
* Let f = a << 16 + b
* 1,953,125 = 0x1d << 16 + 0xcd65
* NSec << 23 = ((a << 16) + b) * ((0x1d << 16) + 0xcd65)
* = (a << 16) * 0x1d << 16) +
* (a << 16) * 0xcd65 +
* b * 0x1d << 16) +
* b * 0xcd65;
*/
/* Break the fractional part up into two values */
a16 = frac >> 16;
b0 = frac & 0xffff;
/* Get the b32 and b0 terms
*
* t32 = (a << 16) * 0x1d << 16)
* t0 = b * 0xcd65
*/
t32 = 0x001d * a16;
t0 = 0xcd65 * b0;
/* Get the first b16 term
*
* (a << 16) * 0xcd65
*/
t16 = 0xcd65 * a16;
/* Add the upper 16-bits to the b32 accumulator */
t32 += (t16 >> 16);
/* Add the lower 16-bits to the b0 accumulator, handling carry to the b32
* accumulator
*/
t16 <<= 16;
if (t0 > (0xffffffff - t16))
{
t32++;
}
t0 += t16;
/* Get the second b16 term
*
* b * (0x1d << 16)
*/
t16 = 0x001d * b0;
/* Add the upper 16-bits to the b32 accumulator */
t32 += (t16 >> 16);
/* Add the lower 16-bits to the b0 accumulator, handling carry to the b32
* accumulator
*/
t16 <<= 16;
if (t0 > (0xffffffff - t16))
{
t32++;
}
t0 += t16;
/* t32 and t0 represent the 64 bit product. Now shift right by 23 bits to
* accomplish the divide by by 2**23.
*/
nsec = (t32 << (32 - 23)) + (t0 >> 23);
#endif
/* Set the system time */
tp.tv_sec = seconds;
tp.tv_nsec = nsec;
clock_settime(CLOCK_REALTIME, &tp);
sinfo("Set time to %lu seconds: %d\n", (unsigned long)tp.tv_sec, ret);
}
/****************************************************************************
* Name: ntpc_daemon
*
* Description:
* This 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, char **argv)
{
struct sockaddr_in server;
struct ntp_datagram_s pkt;
struct timeval tv;
#ifdef CONFIG_LIBC_NETDB
struct hostent *he;
struct in_addr **addr_list;
#endif
socklen_t socklen;
ssize_t nbytes;
int exitcode = EXIT_SUCCESS;
int retry = 0;
int sd;
int ret;
/* Indicate that we have started */
g_ntpc_daemon.state = NTP_RUNNING;
sem_post(&g_ntpc_daemon.interlock);
/* Create a datagram socket */
sd = socket(AF_INET, SOCK_DGRAM, 0);
if (sd < 0)
{
nerr("ERROR: socket failed: %d\n", errno);
g_ntpc_daemon.state = NTP_STOPPED;
sem_post(&g_ntpc_daemon.interlock);
return EXIT_FAILURE;
}
/* 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)
{
nerr("ERROR: setsockopt failed: %d\n", errno);
g_ntpc_daemon.state = NTP_STOPPED;
sem_post(&g_ntpc_daemon.interlock);
return EXIT_FAILURE;
}
/* Setup or sockaddr_in struct with information about the server we are
* going to ask the time from.
*/
memset(&server, 0, sizeof(struct sockaddr_in));
server.sin_family = AF_INET;
server.sin_port = htons(CONFIG_NETUTILS_NTPCLIENT_PORTNO);
#ifndef CONFIG_LIBC_NETDB
server.sin_addr.s_addr = htonl(CONFIG_NETUTILS_NTPCLIENT_SERVERIP);
#else
he = gethostbyname(CONFIG_NETUTILS_NTPCLIENT_SERVER);
if (he != NULL && he->h_addrtype == AF_INET)
{
addr_list = (struct in_addr **)he->h_addr_list;
server.sin_addr.s_addr = addr_list[0]->s_addr;
ninfo("INFO: '%s' resolved to: %s\n",
CONFIG_NETUTILS_NTPCLIENT_SERVER,
inet_ntoa(server.sin_addr));
}
else
{
nerr("ERROR: Failed to resolve '%s'\n", CONFIG_NETUTILS_NTPCLIENT_SERVER);
return EXIT_FAILURE;
}
#endif
/* Here we do the communication with the NTP server. This is a very simple
* client architecture. A request is sent and then a NTP packet is received
* and used to set the current time.
*
* 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)
{
/* Format the transmit datagram */
memset(&pkt, 0, sizeof(pkt));
pkt.lvm = MKLVM(0, 3, NTP_VERSION);
sinfo("Sending a NTP packet\n");
ret = sendto(sd, &pkt, sizeof(struct ntp_datagram_s),
0, (FAR struct sockaddr *)&server,
sizeof(struct sockaddr_in));
if (ret < 0)
{
/* Check if we received a signal. That is not an error but
* other error events will terminate the client.
*/
int errval = errno;
if (errval != EINTR)
{
nerr("ERROR: sendto() failed: %d\n", errval);
exitcode = EXIT_FAILURE;
break;
}
/* Go back to the top of the loop if we were interrupted
* by a signal. The signal might mean that we were
* requested to stop(?)
*/
continue;
}
/* Attempt to receive a packet (with a timeout that was set up via
* setsockopt() above)
*/
socklen = sizeof(struct sockaddr_in);
nbytes = recvfrom(sd, (void *)&pkt, sizeof(struct ntp_datagram_s),
0, (FAR struct sockaddr *)&server, &socklen);
/* Check if the received message was long enough to be a valid NTP
* datagram.
*/
if (nbytes >= (ssize_t)NTP_DATAGRAM_MINSIZE)
{
sinfo("Setting time\n");
ntpc_settime(pkt.recvtimestamp);
retry = 0;
}
/* Check for errors. Note that properly received, short datagrams
* are simply ignored.
*/
else if (nbytes < 0)
{
/* Check if we received a signal. That is not an error but
* other error events will terminate the client.
*/
int errval = errno;
if (errval != EINTR)
{
/* Allow up to three retries */
if (++retry < 3)
{
continue;
}
/* Then declare the failure */
nerr("ERROR: recvfrom() failed: %d\n", errval);
exitcode = EXIT_FAILURE;
break;
}
}
/* 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)
{
sinfo("Waiting for %d seconds\n",
CONFIG_NETUTILS_NTPCLIENT_POLLDELAYSEC);
sleep(CONFIG_NETUTILS_NTPCLIENT_POLLDELAYSEC);
}
}
/* The NTP client is terminating */
sched_unlock();
g_ntpc_daemon.state = NTP_STOPPED;
sem_post(&g_ntpc_daemon.interlock);
return exitcode;
}
/****************************************************************************
* Public Functions
****************************************************************************/
/****************************************************************************
* 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)
{
/* Is the NTP in a non-running state? */
sched_lock();
if (g_ntpc_daemon.state == NTP_NOT_RUNNING ||
g_ntpc_daemon.state == NTP_STOPPED)
{
/* Is this the first time that the NTP daemon has been started? */
if (g_ntpc_daemon.state == NTP_NOT_RUNNING)
{
/* Yes... then we will need to initialize the state structure */
sem_init(&g_ntpc_daemon.interlock, 0, 0);
}
/* 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,
NULL);
/* 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\n", errval);
sched_unlock();
return -errval;
}
/* Wait for any daemon state change */
do
{
sem_wait(&g_ntpc_daemon.interlock);
}
while (g_ntpc_daemon.state == NTP_STARTED);
}
sched_unlock();
return g_ntpc_daemon.pid;
}
/****************************************************************************
* 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? */
sched_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.interlock);
}
while (g_ntpc_daemon.state == NTP_STOP_REQUESTED);
}
sched_unlock();
return OK;
}