A time interface class. This class replicates the normal time functions, but goes a couple of steps further. mbed library 82 and prior has a defective gmtime function. Also, this class enables access to setting the time, and adjusting the accuracy of the RTC.

Dependencies:   CalendarPage

Dependents:   CI-data-logger-server WattEye X10Svr SSDP_Server

NTPClient/NTPClient.cpp

Committer:
WiredHome
Date:
2020-09-13
Revision:
33:e49b25bdbfa5
Parent:
32:00ee2ad29da0

File content as of revision 33:e49b25bdbfa5:

/* NTPClient.cpp */
/* Copyright (C) 2012 mbed.org, MIT License
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy of this software
 * and associated documentation files (the "Software"), to deal in the Software without restriction,
 * including without limitation the rights to use, copy, modify, merge, publish, distribute,
 * sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in all copies or
 * substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING
 * BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
 * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
 * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
 */
#include "mbed.h" //time() and set_time()

#include "EthernetInterface.h"
#include "UDPSocket.h"
#include "Socket.h"
#include "NTPClient.h"


//#define DEBUG "NTPc"

#if (defined(DEBUG))
#include <cstdio>
#define INFO(x, ...) std::printf("[INF %s %4d] "x"\r\n", DEBUG, __LINE__, ##__VA_ARGS__);
#define WARN(x, ...) std::printf("[WRN %s %4d] "x"\r\n", DEBUG, __LINE__, ##__VA_ARGS__);
#define ERR(x, ...)  std::printf("[ERR %s %4d] "x"\r\n", DEBUG, __LINE__, ##__VA_ARGS__);
static void HexDump(const char * title, void * pT, int count)
{
    int i;
    uint8_t * p = (uint8_t *)pT;
    char buf[100] = "0000: ";

    if (*title)
        INFO("%s", title);
    for (i=0; i<count; ) {
        sprintf(buf + strlen(buf), "%02X ", *(p+i));
        if ((++i & 0x0F) == 0x00) {
            INFO("%s", buf);
            if (i < count)
                sprintf(buf, "%04X: ", i);
            else
                buf[0] = '\0';
        }
    }
    if (strlen(buf))
        INFO("%s", buf);
}
#else
//Disable debug
#define INFO(x, ...)
#define WARN(x, ...)
#define ERR(x, ...)
#define HexDump(a,b,c)
#endif


#define NTP_PORT 123
#define NTP_CLIENT_PORT 0       //Random port
#define NTP_TIMESTAMP_DELTA 2208988800ull //Diff btw a UNIX timestamp (Starting Jan, 1st 1970) and a NTP timestamp (Starting Jan, 1st 1900)

#if 0 && MBED_MAJOR_VERSION == 5
#define htonl(x) ((((x) & 0x000000ffUL) << 24) | \
                     (((x) & 0x0000ff00UL) <<  8) | \
                     (((x) & 0x00ff0000UL) >>  8) | \
                     (((x) & 0xff000000UL) >> 24))
#define ntohl(x) htonl(x)
#endif

NTPClient::NTPClient(EthernetInterface * _net) : m_sock()
{
    net = _net;
}


NTPResult NTPClient::setTime(const char* host, uint16_t port, uint32_t timeout)
{
#ifdef DEBUG
    time_t ctTime;
    ctTime = time(NULL);
    INFO("Time is currently (UTC): %s", ctime(&ctTime));
#endif

#if 0 && MBED_MAJOR_VERSION == 5

    //
    // MBED OS 5
    //

    struct NTPPacket pkt;

    SocketAddress nist;
    int ret_gethostbyname = net->gethostbyname(host, &nist);
    INFO("gethostbyname(%s) returned %d", host, ret_gethostbyname);
    if (ret_gethostbyname < 0) {
        return NTP_DNS;     // Network error on DNS lookup
    }

    nist.set_port(port);
    INFO("set_port(%d)", port);

    time_t tQueryTime = time(NULL);
    //
    //Prepare NTP Packet for the query:
    //
    pkt.li = 0;             //Leap Indicator : No warning
    pkt.vn = 4;             //Version Number : 4
    pkt.mode = 3;           //Client mode
    pkt.stratum = 0;        //Not relevant here
    pkt.poll = 0;           //Not significant as well
    pkt.precision = 0;      //Neither this one is
    pkt.rootDelay = 0;      //Or this one
    pkt.rootDispersion = 0; //Or that one
    pkt.refId = 0;          //...
    pkt.refTm_s = 0;
    pkt.origTm_s = 0;
    pkt.rxTm_s = 0;
    pkt.txTm_s = NTP_TIMESTAMP_DELTA + tQueryTime; //WARN: We are in LE format, network byte order is BE
    pkt.refTm_f = pkt.origTm_f = pkt.rxTm_f = pkt.txTm_f = 0;
    HexDump("NTP Post", (uint8_t *)&pkt, sizeof(NTPPacket));
    pkt.txTm_s = htonl(pkt.txTm_s);

    // Contact the server
    UDPSocket sock;
    nsapi_error_t ret = sock.open(net);
    INFO("sock.open(...) returned %d", ret);
    sock.set_timeout(timeout);   //Set timeout, non-blocking and wait using select

    // Send the query
    int ret_send = sock.sendto(nist, (void *)&pkt, sizeof(NTPPacket));
    INFO("sock.sendto(...) returned %d", ret_send);
    
    SocketAddress source;
    
    // Set the inEndpoint address property
    source.set_ip_address(nist.get_ip_address());
    const int n = sock.recvfrom(&source, (void *)&pkt, sizeof(NTPPacket));
    uint32_t destTimeStamp = NTP_TIMESTAMP_DELTA + time(NULL);
    INFO("recvfrom(...) returned %d", n);

    if (pkt.stratum == 0) { //Kiss of death message : Not good !
        ERR("Kissed to death!");
        sock.close();
        return NTP_PRTCL;
    }

    HexDump("NTP Info", (uint8_t *)&pkt, sizeof(NTPPacket));

    //Correct Endianness
    pkt.refTm_s = ntohl( pkt.refTm_s );
    pkt.refTm_f = ntohl( pkt.refTm_f );
    pkt.origTm_s = ntohl( pkt.origTm_s );
    pkt.origTm_f = ntohl( pkt.origTm_f );
    pkt.rxTm_s = ntohl( pkt.rxTm_s );
    pkt.rxTm_f = ntohl( pkt.rxTm_f );
    pkt.txTm_s = ntohl( pkt.txTm_s );
    pkt.txTm_f = ntohl( pkt.txTm_f );

    #ifdef DEBUG
    const char *ModeList[] = {
        "reserved", "symmetric active", "symmetric passive", "client",
        "server", "broadcast", "reserved for NTP ctrl", "reserved for priv use"
    };
    INFO("  pkt.li (Leap Ind)  %d", pkt.li);
    INFO("  pkt.vn (Vers #)    %d", pkt.vn);
    INFO("  pkt.mode           %d, mode %s", pkt.mode, ModeList[pkt.mode]);
    INFO("  pkt.stratum        %d, 0=kiss-o'-death, 1=prim, 2=secd", pkt.stratum);
    INFO("  pkt.poll           %d", pkt.poll);
    INFO("  pkt.precision      %d", pkt.precision);
    INFO("  pkt.rootDelay      %d", pkt.rootDelay);
    INFO("  pkt.rootDispersion %d", pkt.rootDispersion);
    INFO("  pkt.refId          %08X, %u", pkt.refId, pkt.refId);
    INFO("  pkt.refTm_s        %08X, %u, ref time (last set)", pkt.refTm_s, pkt.refTm_s);
    INFO("  pkt.origTm_s       %08X, %u, time sent from client", pkt.origTm_s, pkt.origTm_s);
    INFO("  pkt.rxTm_s         %08X, %u, time rcvd at server", pkt.rxTm_s, pkt.rxTm_s);
    INFO("  pkt.txTm_s         %08X, %u, time sent from server", pkt.txTm_s, pkt.txTm_s);
    INFO("  pkt.refTm_f        %08X, %u, fraction", pkt.refTm_f, pkt.refTm_f);
    #endif
    
    ret = sock.close();
    INFO("sock.close() returned %d", ret);

    if (n == sizeof(NTPPacket)) {

        // Modification by David Smart
        // The setTime function was computing the offset incorrectly as the value was promoted to 64-bit.
        // The side effect was that a negative offset ended up as a very large positive (e.g. jump from 
        // 2016 to 2084). This change revises that computation.
        int64_t offset = (((int64_t)pkt.rxTm_s - pkt.origTm_s) + ((int64_t)pkt.txTm_s - destTimeStamp))/2;
        set_time( time(NULL) + offset );
    } else {
        ERR("bad return from recvfrom() %d", n);
        if (n < 0) {
            // Network error
            return NTP_CONN;
        } else {
            // No or partial data returned
            return NTP_PRTCL;
        }
    }

#else   // MBED OS 2

    //
    // MBED OS 2
    //

    struct NTPPacket pkt;

    Endpoint nist;
    int ret_gethostbyname = nist.set_address(host, port);
    INFO("gethostbyname(%s) returned %d", host, ret_gethostbyname);
    if (ret_gethostbyname < 0) {
        m_sock.close();
        return NTP_DNS;     // Network error on DNS lookup
    }
    INFO("nist: %s:%d", nist.get_address(), nist.get_port());

    //Create & bind socket
    INFO("Binding socket");
    m_sock.bind(0); //Bind to a random port
    m_sock.set_blocking(false, timeout); //Set not blocking

    time_t tQueryTime = time(NULL);
    //
    //Prepare NTP Packet for the query:
    //
    pkt.li = 0;             //Leap Indicator : No warning
    pkt.vn = 4;             //Version Number : 4
    pkt.mode = 3;           //Client mode
    pkt.stratum = 0;        //Not relevant here
    pkt.poll = 0;           //Not significant as well
    pkt.precision = 0;      //Neither this one is
    pkt.rootDelay = 0;      //Or this one
    pkt.rootDispersion = 0; //Or that one
    pkt.refId = 0;          //...
    pkt.refTm_s = 0;
    pkt.origTm_s = 0;
    pkt.rxTm_s = 0;
    pkt.txTm_s = NTP_TIMESTAMP_DELTA + tQueryTime; //WARN: We are in LE format, network byte order is BE
    pkt.refTm_f = pkt.origTm_f = pkt.rxTm_f = pkt.txTm_f = 0;
    HexDump("NTP Post", (uint8_t *)&pkt, sizeof(NTPPacket));
    pkt.txTm_s = htonl(pkt.txTm_s);

    // Contact the server
    // UDPSocket sock;
    // nsapi_error_t ret = sock.open(net);
    // INFO("sock.open(...) returned %d", ret);
    // sock.set_timeout(timeout);   //Set timeout, non-blocking and wait using select

    // Send the query
    int ret = m_sock.sendTo(nist, (char*)&pkt, sizeof(NTPPacket));
    INFO("m_sock.sendto(...) returned %d", ret);
    if (ret < 0 ) {
        ERR("Could not send packet");
        m_sock.close();
        return NTP_CONN;
    }

    //Read response
    Endpoint inEndpoint;
    // Set the inEndpoint address property
    inEndpoint.set_address(nist.get_address(), 0);
    INFO(" inEndpoint instantiated: %s.", inEndpoint.get_address());

    int loopLimit = 20;  // semi-randomly selected so it doesn't hang forever here...
    do {
        ret = m_sock.receiveFrom( inEndpoint, (char*)&pkt, sizeof(NTPPacket) );
        if(ret < 0) {
            ERR("Could not receive packet, err: %d", ret);
            m_sock.close();
            return NTP_CONN;
        }
        INFO(".");
        loopLimit--;
    } while( strcmp(nist.get_address(), inEndpoint.get_address()) != 0 && loopLimit > 0);

    if(ret < (int)sizeof(NTPPacket)) { //TODO: Accept chunks
        ERR("Receive packet size does not match, rcvd %d, expected %d", ret, (int)sizeof(NTPPacket));
        m_sock.close();
        return NTP_PRTCL;
    }

    if (pkt.stratum == 0) { //Kiss of death message : Not good !
        ERR("Kissed to death!");
        m_sock.close();
        return NTP_PRTCL;
    }

    HexDump("NTP Info", (uint8_t *)&pkt, sizeof(NTPPacket));

    //Correct Endianness
    pkt.refTm_s = ntohl( pkt.refTm_s );
    pkt.refTm_f = ntohl( pkt.refTm_f );
    pkt.origTm_s = ntohl( pkt.origTm_s );
    pkt.origTm_f = ntohl( pkt.origTm_f );
    pkt.rxTm_s = ntohl( pkt.rxTm_s );
    pkt.rxTm_f = ntohl( pkt.rxTm_f );
    pkt.txTm_s = ntohl( pkt.txTm_s );
    pkt.txTm_f = ntohl( pkt.txTm_f );

    //Compute offset, see RFC 4330 p.13
    uint32_t destTm_s = (NTP_TIMESTAMP_DELTA + time(NULL));

    // Modification by David Smart
    // The setTime function was computing the offset incorrectly as the value was promoted to 64-bit.
    // The side effect was that a negative offset ended up as a very large positive (e.g. jump from 
    // 2016 to 2084). This change revises that computation.
    int64_t offset = (((int64_t)pkt.rxTm_s - pkt.origTm_s) + ((int64_t)pkt.txTm_s - destTm_s)) / 2; //Avoid overflow
    set_time(time(NULL) + offset);

    #ifdef DEBUG
    const char *ModeList[] = {
        "reserved", "symmetric active", "symmetric passive", "client",
        "server", "broadcast", "reserved for NTP ctrl", "reserved for priv use"
    };
    INFO("  pkt.li (Leap Ind)  %d", pkt.li);
    INFO("  pkt.vn (Vers #)    %d", pkt.vn);
    INFO("  pkt.mode           %d, mode %s", pkt.mode, ModeList[pkt.mode]);
    INFO("  pkt.stratum        %d, 0=kiss-o'-death, 1=prim, 2=secd", pkt.stratum);
    INFO("  pkt.poll           %d", pkt.poll);
    INFO("  pkt.precision      %d", pkt.precision);
    INFO("  pkt.rootDelay      %d", pkt.rootDelay);
    INFO("  pkt.rootDispersion %d", pkt.rootDispersion);
    INFO("  pkt.refId          %08X, %u", pkt.refId, pkt.refId);
    INFO("  pkt.refTm_s        %08X, %u, ref time (last set)", pkt.refTm_s, pkt.refTm_s);
    INFO("  pkt.origTm_s       %08X, %u, time sent from client", pkt.origTm_s, pkt.origTm_s);
    INFO("  pkt.rxTm_s         %08X, %u, time rcvd at server", pkt.rxTm_s, pkt.rxTm_s);
    INFO("  pkt.txTm_s         %08X, %u, time sent from server", pkt.txTm_s, pkt.txTm_s);
    INFO("  pkt.refTm_f        %08X, %u, fraction", pkt.refTm_f, pkt.refTm_f);
    INFO("  offset             %08X, %u", destTm_s, destTm_s);
    #endif

    m_sock.close();

#endif // OS version

    #ifdef DEBUG
    ctTime = time(NULL);
    INFO("      ctime:         %s", ctime(&ctTime));
    #endif
    return NTP_OK;
}