Bonjour/Zerconf library

Dependencies:   mbed

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(127,0,0,1), NTP_CLIENT_PORT, "localhost");
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     //Hex Dump:
00184     DBG("\r\nDump Tx:\r\n");
00185     for(int i = 0; i< sizeof(NTPPacket); i++)
00186     {
00187       DBG("%02x ", *((char*)&m_pkt + i));
00188     }
00189     DBG("\r\n\r\n");
00190     
00191     len = m_pUDPSocket->sendto( (char*)&m_pkt, sizeof(NTPPacket), &m_host );
00192     if(len < sizeof(NTPPacket))
00193       { onResult(NTP_PRTCL); close(); return; }
00194       
00195     m_state = NTP_PONG; 
00196           
00197     break;
00198   
00199   case NTP_PONG:
00200     while((len = m_pUDPSocket->recvfrom( (char*)&m_pkt, sizeof(NTPPacket), &host )) != 0 )
00201     {
00202       if( len <= 0 )
00203         break;
00204       if( !host.getIp().isEq(m_host.getIp()) )
00205         continue; //Not our packet
00206       if( len > 0 )
00207         break;
00208     }
00209     
00210     if(len == 0)
00211       return; //Wait for the next packet
00212     
00213     if(len < 0)
00214       { onResult(NTP_PRTCL); close(); return; }
00215     
00216     if(len < sizeof(NTPPacket)) //TODO: Accept chunks
00217       { onResult(NTP_PRTCL); close(); return; }
00218       
00219     //Hex Dump:
00220     DBG("\r\nDump Rx:\r\n");
00221     for(int i = 0; i< sizeof(NTPPacket); i++)
00222     {
00223       DBG("%02x ", *((char*)&m_pkt + i));
00224     }
00225     DBG("\r\n\r\n");
00226       
00227     if( m_pkt.stratum == 0)  //Kiss of death message : Not good !
00228     {
00229       onResult(NTP_PRTCL); close(); return;
00230     }
00231     
00232     //Correct Endianness
00233     m_pkt.refTm_s = ntohl( m_pkt.refTm_s );
00234     m_pkt.refTm_f = ntohl( m_pkt.refTm_f );
00235     m_pkt.origTm_s = ntohl( m_pkt.origTm_s );
00236     m_pkt.origTm_f = ntohl( m_pkt.origTm_f );
00237     m_pkt.rxTm_s = ntohl( m_pkt.rxTm_s );
00238     m_pkt.rxTm_f = ntohl( m_pkt.rxTm_f );
00239     m_pkt.txTm_s = ntohl( m_pkt.txTm_s );
00240     m_pkt.txTm_f = ntohl( m_pkt.txTm_f );
00241     
00242     //Compute offset, see RFC 4330 p.13
00243     uint32_t destTm_s = (NTP_TIMESTAMP_DELTA + time(NULL));
00244     //int32_t origTm = (int32_t) ((uint64_t) m_pkt.origTm - NTP_TIMESTAMP_DELTA); //Convert in local 32 bits timestamps
00245     //int32_t rxTm = (int32_t) ((uint64_t) m_pkt.rxTm - NTP_TIMESTAMP_DELTA); //Convert in local 32 bits timestamps
00246     //int32_t txTm = (int32_t) ((uint64_t) m_pkt.txTm - NTP_TIMESTAMP_DELTA); //Convert in local 32 bits timestamps
00247    // 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;
00248     int64_t offset = ( (int64_t)( m_pkt.rxTm_s - m_pkt.origTm_s ) + (int64_t) ( m_pkt.txTm_s - destTm_s ) ) / 2; //Avoid overflow
00249     DBG("\r\nSent @%d\r\n", m_pkt.txTm_s);
00250     DBG("\r\nOffset: %d\r\n", offset);
00251     //Set time accordingly
00252     set_time( time(NULL) + (offset /*>> 32*/) );
00253     
00254     onResult(NTP_OK);
00255     close();
00256       
00257     break;
00258   }
00259 }
00260 
00261 void NTPClient::setTimeout(int ms)
00262 {
00263   m_timeout = ms;
00264 }
00265 
00266 void NTPClient::resetTimeout()
00267 {
00268   m_watchdog.reset();
00269   m_watchdog.start();
00270 }
00271 
00272 void NTPClient::onTimeout() //Connection has timed out
00273 {
00274   close();
00275   onResult(NTP_TIMEOUT);
00276 }
00277 
00278 void NTPClient::onDNSReply(DNSReply r)
00279 {
00280   if(m_closed)
00281   {
00282     DBG("\r\nWARN: Discarded\r\n");
00283     return;
00284   }
00285   
00286   if( r != DNS_FOUND )
00287   {
00288     DBG("\r\nCould not resolve hostname.\r\n");
00289     onResult(NTP_DNS);
00290     close();
00291     return;
00292   }
00293   DBG("\r\nDNS resolved.\r\n");
00294   m_pDnsReq->close();
00295   delete m_pDnsReq;
00296   m_pDnsReq=NULL;
00297   open();
00298 }
00299   
00300 void NTPClient::onUDPSocketEvent(UDPSocketEvent e)
00301 {
00302   resetTimeout();
00303   switch(e)
00304   {
00305   case UDPSOCKET_READABLE: //The only event for now
00306     resetTimeout();
00307     process();
00308     break;
00309   }
00310 }
00311 
00312 void NTPClient::onResult(NTPResult r) //Must be called by impl when the request completes
00313 {
00314   if(m_pCbItem && m_pCbMeth)
00315     (m_pCbItem->*m_pCbMeth)(r);
00316   else if(m_pCb)
00317     m_pCb(r);
00318   m_blockingResult = r; //Blocking mode
00319 }
00320 
00321 NTPResult NTPClient::blockingProcess() //Called in blocking mode, calls Net::poll() until return code is available
00322 {
00323   m_blockingResult = NTP_PROCESSING;
00324   do
00325   {
00326     Net::poll();
00327   } while(m_blockingResult == NTP_PROCESSING);
00328   Net::poll(); //Necessary for cleanup
00329   return m_blockingResult;
00330 }