summaryrefslogtreecommitdiff
path: root/apps/netutils/ntpclient/ntpclient.c
diff options
context:
space:
mode:
Diffstat (limited to 'apps/netutils/ntpclient/ntpclient.c')
-rw-r--r--apps/netutils/ntpclient/ntpclient.c482
1 files changed, 482 insertions, 0 deletions
diff --git a/apps/netutils/ntpclient/ntpclient.c b/apps/netutils/ntpclient/ntpclient.c
new file mode 100644
index 000000000..44d44557c
--- /dev/null
+++ b/apps/netutils/ntpclient/ntpclient.c
@@ -0,0 +1,482 @@
+/****************************************************************************
+ * netutils/ntpclient/ntpclient.c
+ *
+ * Copyright (C) 2014 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 <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <time.h>
+#include <sched.h>
+#include <errno.h>
+#include <debug.h>
+
+#include <apps/netutils/ntpclient.h>
+
+#include "ntpv3.h"
+
+/****************************************************************************
+ * Pre-processor Definitions
+ ****************************************************************************/
+/* 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 ntpclient_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 once
+ * instance of the NTP daemon is permitted in this implementation.
+ */
+
+struct ntpclient_daemon_s
+{
+ volatile uint8_t state; /* See enum ntpclient_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 once
+ * instance of the NTP daemon is permitted in this implementation. This
+ * limitation is due only to this global data structure.
+ */
+
+static struct ntpclient_daemon_s g_ntpclient_daemon;
+
+/****************************************************************************
+ * Private Functions
+ ****************************************************************************/
+/****************************************************************************
+ * Name: ntpclient_getuint32
+ *
+ * Description:
+ * Return the big-endian, 4-byte value in network (big-endian) order.
+ *
+ ****************************************************************************/
+
+static inline uint32_t ntpclient_getuint32(FAR uint8_t *ptr)
+{
+ /* Network order is big-endian; host order is irrelevant */
+
+ return (uint32_t)ptr[0] | /* LS byte appears first in data stream */
+ ((uint32_t)ptr[1] << 8) |
+ ((uint32_t)ptr[2] << 16) |
+ ((uint32_t)ptr[3] << 24);
+}
+
+/****************************************************************************
+ * Name: ntpclient_settime
+ *
+ * Description:
+ * Given the NTP time in seconds, set the system time
+ *
+ ****************************************************************************/
+
+static void ntpclient_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 = ntpclient_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 = ntpclient_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++;
+ }
+ else
+ {
+ t0 += t16;
+ }
+
+ /* Get the second b16 term
+ *
+ * b * 0x1d << 16)
+ */
+
+ t16 = 0x001d * b
+
+ /* 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++;
+ }
+ else
+ {
+ 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);
+
+ svdbg("Set time to %ld seconds: %d\n", tp.tv_sec, ret);
+}
+
+/****************************************************************************
+ * Name: ntpclient_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 ntpclient_daemon(int argc, char **argv)
+{
+ struct sockaddr_in server;
+ struct ntp_datagram_s xmit;
+ struct ntp_datagram_s recv;
+ struct timeval tv;
+ socklen_t socklen;
+ ssize_t nbytes;
+ int exitcode = EXIT_SUCCESS;
+ int ret;
+ int sd;
+
+ /* Indicate that we have started */
+
+ g_ntpclient_daemon.state = NTP_RUNNING;
+ sem_post(&g_ntpclient_daemon.interlock);
+
+ /* Create a datagram socket */
+
+ sd = socket(AF_INET, SOCK_DGRAM, 0);
+ if (sd < 0)
+ {
+ ndbg("ERROR: socket failed: %d\n", errno);
+
+ g_ntpclient_daemon.state = NTP_STOPPED;
+ sem_post(&g_ntpclient_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)
+ {
+ ndbg("ERROR: setsockopt failed: %d\n", errno);
+
+ g_ntpclient_daemon.state = NTP_STOPPED;
+ sem_post(&g_ntpclient_daemon.interlock);
+ return EXIT_FAILURE;
+ }
+
+ /* Setup or sockaddr_in struct with information about the server we are
+ * going to ask the the time from.
+ */
+
+ memset(&server, 0, sizeof(struct sockaddr_in));
+ server.sin_family = AF_INET;
+ server.sin_port = htons(CONFIG_NETUTILS_NTPCLIENT_PORTNO);
+ server.sin_addr.s_addr = htonl(CONFIG_NETUTILS_NTPCLIENT_SERVERIP);
+
+ /* 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.
+ * The NTP packet received is decoded to the recv structure for easy
+ * access.
+ */
+
+ while (g_ntpclient_daemon.state == NTP_STOP_REQUESTED)
+ {
+ memset(&xmit, 0, sizeof(xmit));
+ xmit.lvm = MKLVM(0, 3, NTP_VERSION);
+
+ svdbg("Sending a NTP packet\n");
+
+ ret = sendto(sd, &xmit, sizeof(struct ntp_datagram_s),
+ 0, (FAR struct sockaddr *)&server,
+ sizeof(struct sockaddr_in));
+ if (ret < 0)
+ {
+ ndbg("ERROR: sendto() failed: %d\n", errno);
+ exitcode = EXIT_FAILURE;
+ break;
+ }
+
+ /* Attempt to receive a packet (with a timeout) */
+
+ socklen = sizeof(struct sockaddr_in);
+ nbytes = recvfrom(sd, (void *)&recv, sizeof(struct ntp_datagram_s),
+ 0, (FAR struct sockaddr *)&server, &socklen);
+ if (nbytes >= NTP_DATAGRAM_MINSIZE)
+ {
+ svdbg("Setting time\n");
+ ntpclient_settime(recv.recvtimestamp);
+ }
+
+ /* A full implementation of an NTP client would requireq much more. I
+ * think we we can skip that here.
+ */
+
+ svdbg("Waiting for %d seconds\n", CONFIG_NETUTILS_NTPCLIENT_POLLDELAYSEC);
+ (void)sleep(CONFIG_NETUTILS_NTPCLIENT_POLLDELAYSEC);
+ }
+
+ /* The NTP client is terminating */
+
+ g_ntpclient_daemon.state = NTP_STOPPED;
+ sem_post(&g_ntpclient_daemon.interlock);
+ return exitcode;
+}
+
+/****************************************************************************
+ * Public Functions
+ ****************************************************************************/
+/****************************************************************************
+ * Name: ntpclient_start
+ *
+ * Description:
+ * Start the NTP daemon
+ *
+ ****************************************************************************/
+
+int ntpclient_start(void)
+{
+ /* Is the NTP in a non-running state? */
+
+ sched_lock();
+ if (g_ntpclient_daemon.state == NTP_NOT_RUNNING ||
+ g_ntpclient_daemon.state == NTP_STOPPED)
+ {
+ /* Is this the first time that the NTP daemon has been started? */
+
+ if (g_ntpclient_daemon.state == NTP_NOT_RUNNING)
+ {
+ /* Yes... then we will need to initialize the state structure */
+
+ sem_init(&g_ntpclient_daemon.interlock, 0, 0);
+ }
+
+ /* Start the NTP daemon */
+
+ g_ntpclient_daemon.state = NTP_STARTED;
+ g_ntpclient_daemon.pid =
+ TASK_CREATE("NTP daemon", CONFIG_NETUTILS_NTPCLIENT_SERVERPRIO,
+ CONFIG_NETUTILS_NTPCLIENT_STACKSIZE, ntpclient_daemon,
+ NULL);
+
+ /* Handle failures to start the NTP daemon */
+
+ if (g_ntpclient_daemon.pid < 0)
+ {
+ int errval = errno;
+ DEBUGASSERT(errval > 0);
+
+ g_ntpclient_daemon.state = NTP_STOPPED;
+ ndbg("ERROR: Failed to start the NTP daemon\n", errval);
+ return -errval;
+ }
+
+ /* Wait for any daemon state change */
+
+ do
+ {
+ (void)sem_wait(&g_ntpclient_daemon.interlock);
+ }
+ while (g_ntpclient_daemon.state == NTP_STARTED);
+ }
+
+ sched_unlock();
+ return OK;
+}
+
+/****************************************************************************
+ * Name: ntpclient_stop
+ *
+ * Description:
+ * Stop the NTP daemon
+ *
+ ****************************************************************************/
+
+int ntpclient_stop(void)
+{
+ /* Is the NTP in a running state? */
+
+ sched_lock();
+ if (g_ntpclient_daemon.state == NTP_STARTED ||
+ g_ntpclient_daemon.state == NTP_RUNNING)
+ {
+ /* Yes.. request that the daemon stop. */
+
+ g_ntpclient_daemon.state = NTP_STOP_REQUESTED;
+
+ /* Wait for any daemon state change */
+
+ do
+ {
+ (void)sem_wait(&g_ntpclient_daemon.interlock);
+ }
+ while (g_ntpclient_daemon.state == NTP_STOP_REQUESTED);
+ }
+
+ sched_unlock();
+ return OK;
+}