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:
2017-11-21
Revision:
21:f3818e2e0370
Child:
27:67e4e2ab048a

File content as of revision 21:f3818e2e0370:

/* 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

    //
    // MBED OS 5
    //
#if MBED_MAJOR_VERSION == 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;
    pkt.refTm_f = pkt.origTm_f = pkt.rxTm_f = pkt.txTm_f = 0;
    
    INFO("      ctime:         %s", ctime(&tQueryTime));

    //WARN: We are in LE format, network byte order is BE
    INFO("  pkt.txTm_s         %08X, %u, time to send to server", pkt.txTm_s, pkt.txTm_s);
    pkt.txTm_s = htonl(pkt.txTm_s);
    INFO("  pkt.txTm_s         %08X, %u, time to send to server", pkt.txTm_s, pkt.txTm_s);
    HexDump("sending", &pkt, sizeof(NTPPacket));

    UDPSocket sock;
    nsapi_error_t ret = sock.open(net);
    INFO("sock.open(...) returned %d", ret);
    
    sock.set_timeout(timeout);

    int ret_send = sock.sendto(nist, (void *)&pkt, sizeof(NTPPacket));
    INFO("sock.sendto(...) returned %d", ret_send);
    
    SocketAddress source;
    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!");
        m_sock.close();
        return NTP_PRTCL;
    }
    
    HexDump("received", &pkt, sizeof(NTPPacket));
    
    //Correct Endianness
    #if 1
    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 );
    #endif

    #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)) {
        int64_t t;
        #ifdef DEBUG
        uint32_t T1, T2, T3, T4;
        T1 = pkt.origTm_s;
        T2 = pkt.rxTm_s;
        T3 = pkt.txTm_s;
        T4 = destTimeStamp;

        uint32_t d = (T4 - T1) - (T3 - T2);
        INFO("d = %d = (%d - %d) - (%d - %d)", d, T4,T1,T3,T2);
        INFO("d = %d = (  %d   )    -     (  %d  )", d, (T4-T1), (T3-T2));
        t = ((T2 - T1) + (T3 - T4))/2;
        INFO("t = %lld = ((%d - %d) + (%d - %d)) / 2;", t, T2,T1,T3,T4);
        INFO("t = %lld = ((   %d   )  +  (   %d   )) / 2", t, (T2-T1), (T3-T4));
        #endif

        // 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.
        t = (((int64_t)pkt.rxTm_s - pkt.origTm_s) + ((int64_t)pkt.txTm_s - destTimeStamp))/2;
        set_time( time(NULL) + t );
    } 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

    //Create & bind socket
    INFO("Binding socket");
    m_sock.bind(0); //Bind to a random port

    //
    // MBED OS 2
    //
    m_sock.set_blocking(false, timeout); //Set not blocking

    struct NTPPacket pkt;

    //Now ping the server and wait for response
    INFO("Ping");
    //Prepare NTP Packet:
    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 = htonl( NTP_TIMESTAMP_DELTA + time(NULL) ); //WARN: We are in LE format, network byte order is BE
    INFO("pkt.txTm_s = %u", ntohl(pkt.txTm_s) );
    pkt.refTm_f = pkt.origTm_f = pkt.rxTm_f = pkt.txTm_f = 0;

    HexDump("NTP Post", (uint8_t *)&pkt, sizeof(NTPPacket));
    
    Endpoint outEndpoint;
    INFO("outEndpoint instantiated");
    if( outEndpoint.set_address(host, port) < 0) {
        m_sock.close();
        return NTP_DNS;
    }
    INFO("outEndpoint: %s:%d", outEndpoint.get_address(), outEndpoint.get_port());
    //Set timeout, non-blocking and wait using select
    int ret = m_sock.sendTo( outEndpoint, (char*)&pkt, sizeof(NTPPacket) );
    if (ret < 0 ) {
        ERR("Could not send packet");
        m_sock.close();
        return NTP_CONN;
    }

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

    INFO("Pong");
    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");
            m_sock.close();
            return NTP_CONN;
        }
        INFO(".");
        loopLimit--;
    } while( strcmp(outEndpoint.get_address(), inEndpoint.get_address()) != 0 && loopLimit > 0);

    if(ret < (int)sizeof(NTPPacket)) { //TODO: Accept chunks
        ERR("Receive packet size does not match");
        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));
    INFO("destTm_s = %u", destTm_s);
    INFO("pkt.txTm_s = %u", pkt.txTm_s );
    
    // 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
    
    // delay is not needed, this was for diagnostic purposes only.
    //int64_t delay = ((int64_t) destTm_s - pkt.origTm_s) - ((int64_t) pkt.txTm_s - pkt.rxTm_s);
    INFO("txTm_s   @%u", pkt.txTm_s);
    INFO("origTm_s @%u", pkt.origTm_s);
    INFO("rxTm_s   @%u", pkt.rxTm_s);
    INFO("destTm_s @%u", destTm_s);
    INFO("Offset: %lld", offset);
    //INFO("Delay:  %lld", delay);
    INFO(" time:    %u", time(NULL));
    //Set time accordingly
    set_time( time(NULL) + offset );

    m_sock.close();
#endif // OS version

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