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.
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; }