NetServices Stack source

Dependents:   HelloWorld ServoInterfaceBoardExample1 4180_Lab4

Embed: (wiki syntax)

« Back to documentation index

Show/hide line numbers NTPClient.cpp Source File

NTPClient.cpp

00001 
00002 /*
00003 Copyright (c) 2010 Donatien Garnier (donatiengar [at] gmail [dot] com)
00004  
00005 Permission is hereby granted, free of charge, to any person obtaining a copy
00006 of this software and associated documentation files (the "Software"), to deal
00007 in the Software without restriction, including without limitation the rights
00008 to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
00009 copies of the Software, and to permit persons to whom the Software is
00010 furnished to do so, subject to the following conditions:
00011  
00012 The above copyright notice and this permission notice shall be included in
00013 all copies or substantial portions of the Software.
00014  
00015 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
00016 IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
00017 FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
00018 AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
00019 LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
00020 OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
00021 THE SOFTWARE.
00022 */
00023 
00024 #include "NTPClient.h"
00025 
00026 #include <stdio.h>
00027 
00028 //#define __DEBUG
00029 #include "dbg/dbg.h"
00030 
00031 #define NTP_PORT 123
00032 #define NTP_CLIENT_PORT 0//50420 //Random port
00033 #define NTP_REQUEST_TIMEOUT 15000
00034 #define NTP_TIMESTAMP_DELTA 2208988800ull //Diff btw a UNIX timestamp (Starting Jan, 1st 1970) and a NTP timestamp (Starting Jan, 1st 1900)
00035 
00036 #define htons( x ) ( (( x << 8 ) & 0xFF00) | (( x >> 8 ) & 0x00FF) )
00037 #define ntohs( x ) (htons(x))
00038 
00039 #define htonl( x ) ( (( x << 24 ) & 0xFF000000)  \
00040                    | (( x <<  8 ) & 0x00FF0000)  \
00041                    | (( x >>  8 ) & 0x0000FF00)  \
00042                    | (( x >> 24 ) & 0x000000FF)  )
00043 #define ntohl( x ) (htonl(x))
00044 
00045 NTPClient::NTPClient() : NetService(false), m_state(NTP_PING), m_pCbItem(NULL), m_pCbMeth(NULL), m_pCb(NULL),
00046 m_watchdog(), m_timeout(0), m_closed(true), m_host(), m_pDnsReq(NULL), m_blockingResult(NTP_PROCESSING)
00047 {
00048   setTimeout(NTP_REQUEST_TIMEOUT);
00049   DBG("\r\nNew NTPClient %p\r\n",this);
00050 }
00051 
00052 NTPClient::~NTPClient()
00053 {
00054   close();
00055 }
00056 
00057 //High level setup functions
00058 NTPResult NTPClient::setTime(const Host& host) //Blocking
00059 {
00060   doSetTime(host);
00061   return blockingProcess();
00062 }
00063 
00064 NTPResult NTPClient::setTime(const Host& host, void (*pMethod)(NTPResult)) //Non blocking
00065 {
00066   setOnResult(pMethod);
00067   doSetTime(host);
00068   return NTP_PROCESSING;
00069 }
00070 
00071 #if 0 //For doc only
00072 template<class T> 
00073 NTPResult NTPClient::setTime(const Host& host, T* pItem, void (T::*pMethod)(NTPResult)) //Non blocking
00074 {
00075   setOnResult(pItem, pMethod);
00076   doSetTime(host);
00077   return NTP_PROCESSING;
00078 }
00079 #endif
00080 
00081 void NTPClient::doSetTime(const Host& host)
00082 {
00083   init();
00084   resetTimeout();
00085   m_host = host;
00086   if(!m_host.getPort())
00087   {
00088     m_host.setPort(NTP_PORT);
00089   }
00090   if(m_host.getIp().isNull())
00091   {
00092     //DNS query required
00093     m_pDnsReq = new DNSRequest();
00094     DBG("\r\nNTPClient : DNSRequest %p\r\n", m_pDnsReq);
00095     m_pDnsReq->setOnReply(this, &NTPClient::onDNSReply);
00096     m_pDnsReq->resolve(&m_host);
00097     return;
00098   }
00099   open();
00100 }
00101 
00102 void NTPClient::setOnResult( void (*pMethod)(NTPResult) )
00103 {
00104   m_pCb = pMethod;
00105 }
00106 
00107 void NTPClient::close()
00108 {
00109   if(m_closed)
00110     return;
00111   m_closed = true; //Prevent recursive calling or calling on an object being destructed by someone else
00112   m_watchdog.stop();
00113   m_pUDPSocket->resetOnEvent();
00114   m_pUDPSocket->close();
00115   delete m_pUDPSocket;
00116   if( m_pDnsReq )
00117   {
00118     m_pDnsReq->close();
00119     delete m_pDnsReq;
00120     m_pDnsReq = NULL;
00121   }
00122 }
00123 
00124 void NTPClient::poll() //Called by NetServices
00125 {
00126   if( (!m_closed) && (m_watchdog.read_ms() >= m_timeout) )
00127   {
00128     onTimeout();
00129   }
00130 }
00131 
00132 void NTPClient::init() //Create and setup socket if needed
00133 {
00134   if(!m_closed) //Already opened
00135     return;
00136   m_state = NTP_PING;
00137   m_pUDPSocket = new UDPSocket;
00138   m_pUDPSocket->setOnEvent(this, &NTPClient::onUDPSocketEvent);
00139   m_closed = false;
00140   DBG("NTPClient: Init OK\n");
00141 }
00142 
00143 void NTPClient::open()
00144 {
00145   resetTimeout();
00146   DBG("Opening connection\n");
00147   m_state = NTP_PING;
00148   Host localhost(IpAddr(), NTP_CLIENT_PORT, "localhost"); //Any local address
00149   m_pUDPSocket->bind(localhost);
00150   set_time( 1280000000 ); //End of July 2010... just there to limit offset range
00151   process();
00152 }
00153 
00154 #define MIN(a,b) ((a)<(b))?(a):(b)
00155 void NTPClient::process() //Main state-machine
00156 {
00157   int len;
00158   Host host;
00159   
00160   switch(m_state)
00161   {
00162   case NTP_PING:
00163     DBG("\r\nPing\r\n");
00164     //Prepare NTP Packet:
00165     m_pkt.li = 0; //Leap Indicator : No warning
00166     m_pkt.vn = 4; //Version Number : 4
00167     m_pkt.mode = 3; //Client mode
00168     m_pkt.stratum = 0; //Not relevant here
00169     m_pkt.poll = 0; //Not significant as well
00170     m_pkt.precision = 0; //Neither this one is
00171     
00172     m_pkt.rootDelay = 0; //Or this one
00173     m_pkt.rootDispersion = 0; //Or that one
00174     m_pkt.refId = 0; //...
00175     
00176     m_pkt.refTm_s = 0;
00177     m_pkt.origTm_s = 0;
00178     m_pkt.rxTm_s = 0;
00179     m_pkt.txTm_s = htonl( NTP_TIMESTAMP_DELTA + time(NULL) ); //WARN: We are in LE format, network byte order is BE
00180     
00181     m_pkt.refTm_f = m_pkt.origTm_f = m_pkt.rxTm_f = m_pkt.txTm_f = 0;
00182     
00183     #ifdef __DEBUG
00184     //Hex Dump:
00185     DBG("\r\nDump Tx:\r\n");
00186     for(int i = 0; i< sizeof(NTPPacket); i++)
00187     {
00188       DBG("%02x ", *((char*)&m_pkt + i));
00189     }
00190     DBG("\r\n\r\n");
00191     #endif
00192     
00193     len = m_pUDPSocket->sendto( (char*)&m_pkt, sizeof(NTPPacket), &m_host );
00194     if(len < sizeof(NTPPacket))
00195       { onResult(NTP_PRTCL); close(); return; }
00196       
00197     m_state = NTP_PONG; 
00198           
00199     break;
00200   
00201   case NTP_PONG:
00202     DBG("\r\nPong\r\n");
00203     while( len = m_pUDPSocket->recvfrom( (char*)&m_pkt, sizeof(NTPPacket), &host ) )
00204     {
00205       if( len <= 0 )
00206         break;
00207       if( !host.getIp().isEq(m_host.getIp()) )
00208         continue; //Not our packet
00209       if( len > 0 )
00210         break;
00211     }
00212     
00213     if(len == 0)
00214       return; //Wait for the next packet
00215     
00216     if(len < 0)
00217       { onResult(NTP_PRTCL); close(); return; }
00218     
00219     if(len < sizeof(NTPPacket)) //TODO: Accept chunks
00220       { onResult(NTP_PRTCL); close(); return; }
00221       
00222     #ifdef __DEBUG
00223     //Hex Dump:
00224     DBG("\r\nDump Rx:\r\n");
00225     for(int i = 0; i< sizeof(NTPPacket); i++)
00226     {
00227       DBG("%02x ", *((char*)&m_pkt + i));
00228     }
00229     DBG("\r\n\r\n");
00230     #endif
00231       
00232     if( m_pkt.stratum == 0)  //Kiss of death message : Not good !
00233     {
00234       onResult(NTP_PRTCL); close(); return;
00235     }
00236     
00237     //Correct Endianness
00238     m_pkt.refTm_s = ntohl( m_pkt.refTm_s );
00239     m_pkt.refTm_f = ntohl( m_pkt.refTm_f );
00240     m_pkt.origTm_s = ntohl( m_pkt.origTm_s );
00241     m_pkt.origTm_f = ntohl( m_pkt.origTm_f );
00242     m_pkt.rxTm_s = ntohl( m_pkt.rxTm_s );
00243     m_pkt.rxTm_f = ntohl( m_pkt.rxTm_f );
00244     m_pkt.txTm_s = ntohl( m_pkt.txTm_s );
00245     m_pkt.txTm_f = ntohl( m_pkt.txTm_f );
00246     
00247     //Compute offset, see RFC 4330 p.13
00248     uint32_t destTm_s = (NTP_TIMESTAMP_DELTA + time(NULL));
00249     //int32_t origTm = (int32_t) ((uint64_t) m_pkt.origTm - NTP_TIMESTAMP_DELTA); //Convert in local 32 bits timestamps
00250     //int32_t rxTm = (int32_t) ((uint64_t) m_pkt.rxTm - NTP_TIMESTAMP_DELTA); //Convert in local 32 bits timestamps
00251     //int32_t txTm = (int32_t) ((uint64_t) m_pkt.txTm - NTP_TIMESTAMP_DELTA); //Convert in local 32 bits timestamps
00252    // int64_t offset = ( ( ( m_pkt.rxTm_s - m_pkt.origTm_s ) + ( m_pkt.txTm_s - destTm_s ) ) << 32 + ( ( m_pkt.rxTm_f - m_pkt.origTm_f ) + ( m_pkt.txTm_f - 0 ) ) ) / 2;
00253     int64_t offset = ( (int64_t)( m_pkt.rxTm_s - m_pkt.origTm_s ) + (int64_t) ( m_pkt.txTm_s - destTm_s ) ) / 2; //Avoid overflow
00254     DBG("\r\nSent @%d\r\n", m_pkt.txTm_s);
00255     DBG("\r\nOffset: %d\r\n", offset);
00256     //Set time accordingly
00257     set_time( time(NULL) + (offset /*>> 32*/) );
00258     
00259     onResult(NTP_OK);
00260     close();
00261       
00262     break;
00263   }
00264 }
00265 
00266 void NTPClient::setTimeout(int ms)
00267 {
00268   m_timeout = ms;
00269 }
00270 
00271 void NTPClient::resetTimeout()
00272 {
00273   m_watchdog.reset();
00274   m_watchdog.start();
00275 }
00276 
00277 void NTPClient::onTimeout() //Connection has timed out
00278 {
00279   close();
00280   onResult(NTP_TIMEOUT);
00281 }
00282 
00283 void NTPClient::onDNSReply(DNSReply r)
00284 {
00285   if(m_closed)
00286   {
00287     DBG("\r\nWARN: Discarded\r\n");
00288     return;
00289   }
00290   
00291   if( r != DNS_FOUND )
00292   {
00293     DBG("\r\nCould not resolve hostname.\r\n");
00294     onResult(NTP_DNS);
00295     close();
00296     return;
00297   }
00298   DBG("\r\nDNS resolved.\r\n");
00299   m_pDnsReq->close();
00300   delete m_pDnsReq;
00301   m_pDnsReq=NULL;
00302   open();
00303 }
00304   
00305 void NTPClient::onUDPSocketEvent(UDPSocketEvent e)
00306 {
00307   resetTimeout();
00308   switch(e)
00309   {
00310   case UDPSOCKET_READABLE: //The only event for now
00311     resetTimeout();
00312     process();
00313     break;
00314   }
00315 }
00316 
00317 void NTPClient::onResult(NTPResult r) //Must be called by impl when the request completes
00318 {
00319   if(m_pCbItem && m_pCbMeth)
00320     (m_pCbItem->*m_pCbMeth)(r);
00321   else if(m_pCb)
00322     m_pCb(r);
00323   m_blockingResult = r; //Blocking mode
00324 }
00325 
00326 NTPResult NTPClient::blockingProcess() //Called in blocking mode, calls Net::poll() until return code is available
00327 {
00328   m_blockingResult = NTP_PROCESSING;
00329   do
00330   {
00331     Net::poll();
00332   } while(m_blockingResult == NTP_PROCESSING);
00333   Net::poll(); //Necessary for cleanup
00334   return m_blockingResult;
00335 }