// SntpTest.cpp : Defines the entry point for the console application.
//
#include "stdafx.h"
#include <math.h>
#include <time.h>
#include <winsock2.h>
#pragma comment(lib, "wsock32.lib")
//ref : www.ietf.org/rfc/rfc2030.txt
/* 1 2 3
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|LI | VN |Mode | Stratum | Poll | Precision |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Root Delay |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Root Dispersion |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Reference Identifier |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| |
| Reference Timestamp (64) |
| |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| |
| Originate Timestamp (64) |
| |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| |
| Receive Timestamp (64) |
| |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| |
| Transmit Timestamp (64) |
| |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Key Identifier (optional) (32) |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| |
| |
| Message Digest (optional) (128) |
| |
| |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
*/
#pragma pack(push, 1)
typedef struct _tagNTPTimestamp_t
{
unsigned char Seconds[4]; //(32-bit unsigned fixed-point number. Big-endian)
unsigned char Seconds_Fraction[4]; //((32-bit unsigned fixed-point number. Big-endian)
}NTPTIMESTAMP, *LPNTPTIMESTAMP;
typedef struct _tagNTPMsgFmtV4_t
{
struct
{
unsigned char LI:2; //Leap Indicator
//0 no warning
//1 last minute has 61 seconds
//2 last minute has 59 seconds)
//3 alarm condition (clock not synchronized)
unsigned char VN:3; //Version Number
//3 for Version 3 (IPv4 only)
//4 for Version 4 (IPv4, IPv6 and OSI)
unsigned char Mode:3; //indicating the mode
//0 reserved
//1 symmetric active
//2 symmetric passive
//3 client
//4 server
//5 broadcast
//6 reserved for NTP control message
//7 reserved for private use
unsigned char Stratum; //stratum level of the local clock
//0 unspecified or unavailable
//1 primary reference (e.g., radio clock)
//2-15 secondary reference (via NTP or SNTP)
//16-255 reserved
unsigned char Poll; //Poll Interval
unsigned char Precision; //Precision
};
unsigned char Root_Delay[4]; //Root Delay (32-bit signed fixed-point number. Big-endian)
unsigned char Root_Dispersion[4]; //Root Dispersion (32-bit unsigned fixed-point number. Big-endian)
char Reference_Identifier[4]; //Reference Identifier
//LOCL uncalibrated local clock used as a primary reference for a subnet without external means of synchronization
//PPS atomic clock or other pulse-per-second source individually calibrated to national standards
//ACTS NIST dialup modem service
//USNO USNO modem service
//PTB PTB (Germany) modem service
//TDF Allouis (France) Radio 164 kHz
//DCF Mainflingen (Germany) Radio 77.5 kHz
//MSF Rugby (UK) Radio 60 kHz
//WWV Ft. Collins (US) Radio 2.5, 5, 10, 15, 20 MHz
//WWVB Boulder (US) Radio 60 kHz
//WWVH Kaui Hawaii (US) Radio 2.5, 5, 10, 15 MHz
//CHU Ottawa (Canada) Radio 3330, 7335, 14670 kHz
//LORC LORAN-C radionavigation system
//OMEG OMEGA radionavigation system
//GPS Global Positioning Service
//GOES Geostationary Orbit Environment Satellite
NTPTIMESTAMP Reference_Timestamp; //Reference Timestamp (64-bit timestamp format. Big-endian)
NTPTIMESTAMP Originate_Timestamp; //Originate Timestamp (64-bit timestamp format. Big-endian)
NTPTIMESTAMP Receive_Timestamp; //ReceiveTimestamp (64-bit timestamp format. Big-endian)
NTPTIMESTAMP Transmit_Timestamp; //Transmit Timestamp (64-bit timestamp format. Big-endian)
//char Key_Identifier[4]; //Key Identifier (optional)
//unsigned char Message_Digest[16]; //Message Digest (optional)
}NTPMSGFMTV4, *LPNTPMSGFMTV4;
#pragma pack(pop)
//convert NTP timer to system time
BOOL _SNTPTimeToSystemTime(LPNTPTIMESTAMP pNTPTimeStame, SYSTEMTIME &tmDest)
{
// Verifies will fail if the needed buffer size is too large
#define _MIN_DATE (-657434L) // about year 100
#define _MAX_DATE 2958465L // about year 9999
// Half a second, expressed in days
#define _HALF_SECOND (1.0/172800.0)
double dtSrc = 0;
double fMillsecods = 0;
//convert NTP time to days unit
{
u_long uSeconds = ntohl( *((u_long*)&pNTPTimeStame->Seconds[0]) ); //seconds
u_long uSecondsF = ntohl( *((u_long*)&pNTPTimeStame->Seconds_Fraction[0]) ); //Seconds Fraction
//convert Seconds Fraction to double type
double fSecondF = 0;
{
u_long uSecondF =uSecondsF;
int iLen = 0;
double fPart[64] = {0};
while(uSecondF && iLen < 64-1)
{
fPart[iLen++] = (uSecondF % 10);
uSecondF = uSecondF/10;
}
double fDiv = 10.0;
while((--iLen) >= 0)
{
fSecondF += fPart[iLen]/fDiv;
fDiv *= 10;
}
}
double vtDate = uSeconds;
if( pNTPTimeStame->Seconds[0] & 0x80 ) //since from 1900,January,1,00:00:00 to 2036
{
}
else //since form 6h 28m 16s UTC on 7 February 2036
{
vtDate += 0xFFFFFFFFU; //overflow
}
vtDate += (2 * 24 * 3600); //system time Dec. 30, 1899 to NTP Jan.1, 1900
dtSrc = vtDate / (24 * 60 * 60); //convert to days
fMillsecods = (int)(fSecondF * 1000.0);
}
if (dtSrc > _MAX_DATE || dtSrc < _MIN_DATE) // about year 100 to about 9999
return FALSE;
// The legal range does not actually span year 0 to 9999.
long nDays; // Number of days since Dec. 30, 1899
long nDaysAbsolute; // Number of days since 1/1/0
long nSecsInDay; // Time in seconds since midnight
long nMinutesInDay; // Minutes in day
long n400Years; // Number of 400 year increments since 1/1/0
long n400Century; // Century within 400 year block (0,1,2 or 3)
long n4Years; // Number of 4 year increments since 1/1/0
long n4Day; // Day within 4 year block
// (0 is 1/1/yr1, 1460 is 12/31/yr4)
long n4Yr; // Year within 4 year block (0,1,2 or 3)
BOOL bLeap4 = TRUE; // TRUE if 4 year block includes leap year
double dblDate = dtSrc; // tempory serial date
// If a valid date, then this conversion should not overflow
nDays = (long)dblDate;
// Round to the second
dblDate += ((dtSrc > 0.0) ? _HALF_SECOND : -_HALF_SECOND);
nDaysAbsolute = (long)dblDate + 693959L; // Add days from 1/1/0 to 12/30/1899
dblDate = fabs(dblDate);
nSecsInDay = (long)((dblDate - floor(dblDate)) * 86400.);
// Calculate the day of week (sun=0, mon=1...)
tmDest.wDayOfWeek = (int)((nDaysAbsolute - 1) % 7L) + 0;
// Leap years every 4 yrs except centuries not multiples of 400.
n400Years = (long)(nDaysAbsolute / 146097L);
// Set nDaysAbsolute to day within 400-year block
nDaysAbsolute %= 146097L;
// -1 because first century has extra day
n400Century = (long)((nDaysAbsolute - 1) / 36524L);
// Non-leap century
if (n400Century != 0)
{
// Set nDaysAbsolute to day within century
nDaysAbsolute = (nDaysAbsolute - 1) % 36524L;
// +1 because 1st 4 year increment has 1460 days
n4Years = (long)((nDaysAbsolute + 1) / 1461L);
if (n4Years != 0)
{
n4Day = (long)((nDaysAbsolute + 1) % 1461L);
}
else
{
bLeap4 = FALSE;
n4Day = (long)nDaysAbsolute;
}
}
else
{
// Leap century - not special case!
n4Years = (long)(nDaysAbsolute / 1461L);
n4Day = (long)(nDaysAbsolute % 1461L);
}
if (bLeap4)
{
// -1 because first year has 366 days
n4Yr = (n4Day - 1) / 365;
if (n4Yr != 0)
n4Day = (n4Day - 1) % 365;
}
else
{
n4Yr = n4Day / 365;
n4Day %= 365;
}
// n4Day is now 0-based day of year. Save 1-based day of year, year number
int tm_yday = (int)n4Day + 1; /* days since January 1 - [0,365] */
tmDest.wYear = (WORD)(n400Years * 400 + n400Century * 100 + n4Years * 4 + n4Yr);
do
{
// Handle leap year: before, on, and after Feb. 29.
if (n4Yr == 0 && bLeap4)
{
// Leap Year
if (n4Day == 59)
{
/* Feb. 29 */
tmDest.wMonth = 2;
tmDest.wDay = 29;
break; //goto DoTime;
}
// Pretend it's not a leap year for month/day comp.
if (n4Day >= 60)
--n4Day;
}
// Make n4DaY a 1-based day of non-leap year and compute
// month/day for everything but Feb. 29.
++n4Day;
// Month number always >= n/32, so save some loop time */
// One-based array of days in year at month start
static int _MonthDays[13] =
{0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334, 365};
for (tmDest.wMonth = (n4Day >> 5) + 1;
n4Day > _MonthDays[tmDest.wMonth]; tmDest.wMonth++);
tmDest.wDay = (int)(n4Day - _MonthDays[tmDest.wMonth-1]);
}while(0);
//DoTime:
if (nSecsInDay == 0)
{
tmDest.wHour = tmDest.wMinute = tmDest.wSecond = 0;
}
else
{
tmDest.wSecond = (WORD)(nSecsInDay % 60L);
nMinutesInDay = nSecsInDay / 60L;
tmDest.wMinute = (WORD)(nMinutesInDay % 60);
tmDest.wHour = (WORD)(nMinutesInDay / 60);
}
tmDest.wMilliseconds = (WORD)fMillsecods;
return TRUE;
}
int _SNTPGetUTCTime(const char *szSntpServer, SYSTEMTIME *ptmSys)
{
BOOL bRet = 0;
do
{
//init socket
int err = 0;
WSADATA wsa;
err = WSAStartup(MAKEWORD(2, 0), &wsa);
if(err != 0)
{
bRet = -1;
break;
}
SOCKET sSession = INVALID_SOCKET;
do
{
//get host ip by name
hostent * hp = gethostbyname(szSntpServer);
if(hp == NULL)
{
bRet = -2;
break;
}
sockaddr_in SvrIp;
SvrIp.sin_family = AF_INET;
memcpy(&SvrIp.sin_addr, hp->h_addr, sizeof(SvrIp.sin_addr));
SvrIp.sin_port = htons(123);
//create socket
sSession = socket(AF_INET, SOCK_DGRAM, 0);
if(sSession == INVALID_SOCKET)
{
bRet = -3;
break;
}
int iSizeSocketIn = sizeof(SOCKADDR_IN);
//sendto
int nTime = 3000;
setsockopt(sSession, SOL_SOCKET, SO_SNDTIMEO, (char *)&nTime, sizeof(nTime));
NTPMSGFMTV4 txMsg; memset(&txMsg, 0, sizeof(txMsg));
txMsg.LI = 3; //alarm condition (clock not synchronized)
txMsg.VN = 4; //4 for Version 4 (IPv4, IPv6 and OSI)
txMsg.Mode = 4; //4 server
if(SOCKET_ERROR == sendto(sSession, (const char*)&txMsg, sizeof(txMsg), 0, (sockaddr*)&SvrIp, iSizeSocketIn))
{
bRet = -4;
break;
}
//recvfrom
setsockopt(sSession, SOL_SOCKET, SO_RCVTIMEO, (char *)&nTime, sizeof(nTime));
NTPMSGFMTV4 rxMsg; memset(&rxMsg, 0, sizeof(rxMsg));
int nLenRcv = recvfrom(sSession, (char *)&rxMsg, sizeof(rxMsg), 0, (sockaddr*)&SvrIp, &iSizeSocketIn);
if(nLenRcv == SOCKET_ERROR)
{
bRet = -5;
break;
}
//use convert SNTP date to system time
SYSTEMTIME tmSys;
if(!_SNTPTimeToSystemTime(&rxMsg.Transmit_Timestamp, tmSys))
{
bRet = -6;
break;
}
if(ptmSys)
{
*ptmSys = tmSys;
}
bRet = 1;
}while(0);
//close socket
if(sSession != INVALID_SOCKET)
{
closesocket(sSession);
sSession = INVALID_SOCKET;
}
//WASClearup
WSACleanup();
}while(0);
return bRet;
}
int TestSNTP()
{
LPCTSTR szTimeSvr[]=
{
"time.windows.com",
"time.nist.gov",
"time-nw.nist.gov",
"pool.ntp.org",
"europe.pool.ntp.org",
"asia.pool.ntp.org",
"oceania.pool.ntp.org",
"north-america.pool.ntp.org",
"south-america.pool.ntp.org",
"africa.pool.ntp.org",
"ca.pool.ntp.org",
"uk.pool.ntp.org",
"us.pool.ntp.org",
"au.pool.ntp.org",
0
};
for(size_t i = 0; szTimeSvr[i] ; ++i)
{
SYSTEMTIME tm = {0};
if(_SNTPGetUTCTime(szTimeSvr[i], &tm) > 0)
{
printf("%s: %04dY-%02dM-%02dD (Week %d) %02dH:%02dM:%02dS.%03dms\n",
szTimeSvr[i],
tm.wYear, tm.wMonth, tm.wDay, tm.wDayOfWeek,
tm.wHour, tm.wMinute, tm.wSecond, tm.wMilliseconds);
}
}
return 0;
}
int main(int argc, char* argv[])
{
TestSNTP();
printf("Hello World!\n");
return 0;
}
/* debug output
time.windows.com: 2015Y-05M-25D (Week 1) 07H:05M:20S.915ms
time.nist.gov: 2015Y-05M-25D (Week 1) 07H:05M:20S.133ms
pool.ntp.org: 2015Y-05M-25D (Week 1) 07H:05M:24S.573ms
oceania.pool.ntp.org: 2015Y-05M-25D (Week 1) 07H:05M:31S.116ms
south-america.pool.ntp.org: 2015Y-05M-25D (Week 1) 07H:05M:35S.617ms
africa.pool.ntp.org: 2015Y-05M-25D (Week 1) 07H:05M:35S.235ms
ca.pool.ntp.org: 2015Y-05M-25D (Week 1) 07H:05M:35S.405ms
uk.pool.ntp.org: 2015Y-05M-25D (Week 1) 07H:05M:36S.580ms
us.pool.ntp.org: 2015Y-05M-25D (Week 1) 07H:05M:36S.185ms
au.pool.ntp.org: 2015Y-05M-25D (Week 1) 07H:05M:36S.328ms
Hello World!
Press any key to continue
*/