#define UNICODE		1
#define _UNICODE	1

#include <windows.h>
#include <stdio.h>
#include <winsock.h>

#include <svsutil.hxx>

#if ! defined (UNDER_CE)
//#define	NTDBG_DEBUGGER	1
#include "../../osinternal/pbtools/privdbg/ntdbg.h"
#endif

#if defined (DEBUG) || defined (_DEBUG)
DBGPARAM dpCurSettings = 
{
    TEXT("TIMESVC"), 
    {
        TEXT("INIT"),
        TEXT("SERVER"),       
        TEXT("CLIENT"),
        TEXT("PACKETS"),
        TEXT("TRACE"),
        TEXT("<unused>"),
        TEXT("<unused>"),
        TEXT("<unused>"),
        TEXT("<unused>"),
        TEXT("<unused>"),
        TEXT("<unused>"),       
        TEXT("<unused>"),
        TEXT("<unused>"),       
        TEXT("<unused>"),
        TEXT("Warning"),
        TEXT("Error"),
    },

    0xffff
}; 

#define ZONE_INIT               DEBUGZONE(0)        
#define ZONE_SERVER             DEBUGZONE(1)        
#define ZONE_CLIENT             DEBUGZONE(2)        
#define ZONE_PACKETS            DEBUGZONE(3)        
#define ZONE_TRACE              DEBUGZONE(4)        

#define ZONE_MISC               DEBUGZONE(13)
#define ZONE_WARNING            DEBUGZONE(14)       
#define ZONE_ERROR              DEBUGZONE(15)       

#endif	// DEBUG

#if ! defined(DNS_MAX_NAME_BUFFER_LENGTH)
#define DNS_MAX_NAME_BUFFER_LENGTH      (256)
#endif

#define BASE_KEY		L"services\\timesvc"
#define NTP_PORT		123
#define MIN_REFRESH		(5*60*1000)
#define MIN_MULTICAST	(5*60*1000)
#define MIN_TIMEUPDATE	(5*1000)

#define NTPTIMEOFFSET (0x014F373BFDE04000)
#define FIVETOTHESEVETH (0x001312D)

#define RK_CLOCK        TEXT("Software\\Microsoft\\Clock")
#define RV_INDST        TEXT("HomeDST")  //are we in DST?
#define RV_AUTODST      TEXT("AutoDST")  //do we auto-adjust DST?

// FILETIME (100-ns intervals) to minutes (10 x 1000 x 1000 x 60)
#define FILETIME_TO_MINUTES (10 * 1000 * 1000 * 60)
#define FILETIME_TO_MILLISECONDS ((__int64)10000L)

void SetDstOrStd(BOOL fIsAutoDstEnabled, SYSTEMTIME *pST);
void RefreshExplorerDST();

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

///////////////////////////////////////////////////////////////////////
//
//		Base Classes
//
//
class NTP_REQUEST {
private:
	union {
		struct {
			unsigned char i_lvm, i_stratum, i_poll, i_prec;
			unsigned char i_root_delay[4];
			unsigned char i_root_dispersion[4];
			unsigned char i_ref_identifier[4];
			unsigned char i_ref_stamp[8];
			unsigned char i_orig_stamp[8];
			unsigned char i_recv_stamp[8];
			unsigned char i_trans_stamp[8];
		};
		unsigned __int64 ll;
	};

public:
	unsigned int li (void) {
		return (i_lvm >> 6) & 0x03;
	}

	void set_li (unsigned int l) {
		i_lvm = (i_lvm & 0x3f) | ((l & 0x3) << 6);
	}

	unsigned int vn (void) {
		return (i_lvm >> 3) & 0x07;
	}

	void set_vn (unsigned int v) {
		i_lvm = (i_lvm & 0xc7) | ((v & 0x7) << 3);
	}

	unsigned int mode (void) {
		return i_lvm & 0x07;
	}

	void set_mode (unsigned int m) {
		i_lvm = (i_lvm & 0xf8) | (m & 0x7);
	}

	unsigned int stratum (void) {
		return i_stratum;
	}

	void set_stratum (unsigned int s) {
		i_stratum = s;
	}

	unsigned int poll (void) {
		return i_poll;
	}

	void set_poll (unsigned int p) {
		i_poll = p;
	}

	unsigned int precision (void) {
		return i_prec;
	}

	void set_precision (unsigned int p) {
		i_prec = p;
	}

	unsigned int root_delay (void) {
		return (i_root_delay[0] << 24) | (i_root_delay[1] << 16) | (i_root_delay[2] << 8) | i_root_delay[3];
	}

	void set_root_delay (unsigned int rd) {
		i_root_delay[0] = (unsigned char)(rd >> 24);
		i_root_delay[1] = (unsigned char)(rd >> 16);
		i_root_delay[2] = (unsigned char)(rd >>  8);
		i_root_delay[3] = (unsigned char) rd;
	}

	unsigned int root_dispersion (void) {
		return (i_root_dispersion[0] << 24) | (i_root_dispersion[1] << 16) | (i_root_dispersion[2] << 8) | i_root_dispersion[3];
	}

	void set_root_dispersion (unsigned int rd) {
		i_root_dispersion[0] = (unsigned char)(rd >> 24);
		i_root_dispersion[1] = (unsigned char)(rd >> 16);
		i_root_dispersion[2] = (unsigned char)(rd >>  8);
		i_root_dispersion[3] = (unsigned char) rd;
	}

	unsigned int ref_id (void) {
		return (i_ref_identifier[0] << 24) | (i_ref_identifier[1] << 16) | (i_ref_identifier[2] << 8) | i_ref_identifier[3];
	}

	void set_ref_id (unsigned int rid) {
		i_ref_identifier[0] = (unsigned char)(rid >> 24);
		i_ref_identifier[1] = (unsigned char)(rid >> 16);
		i_ref_identifier[2] = (unsigned char)(rid >>  8);
		i_ref_identifier[3] = (unsigned char) rid;
	}

	unsigned __int64 ref_stamp (void) {
		return *(unsigned __int64 *)i_ref_stamp;
	}

	void set_ref_stamp (unsigned __int64 ll) {
		*(unsigned __int64 *)i_ref_stamp = ll;
	}

	unsigned __int64 orig_stamp (void) {
		return *(unsigned __int64 *)i_orig_stamp;
	}

	void set_orig_stamp (unsigned __int64 ll) {
		*(unsigned __int64 *)i_orig_stamp = ll;
	}

	unsigned __int64 recv_stamp (void) {
		return *(unsigned __int64 *)i_recv_stamp;
	}

	void set_recv_stamp (unsigned __int64 ll) {
		*(unsigned __int64 *)i_recv_stamp = ll;
	}

	unsigned __int64 trans_stamp (void) {
		return *(unsigned __int64 *)i_trans_stamp;
	}

	void set_trans_stamp (unsigned __int64 ll) {
		*(unsigned __int64 *)i_trans_stamp = ll;
	}
};

enum When {
	Now,
	Shortly,
	Later
};

class TimeState : public SVSSynch {
	SVSThreadPool	*pEvents;
	SOCKET			sServer;
	DWORD			dwRefreshMS;
	DWORD           dwRecoveryRefreshMS;
	DWORD			dwAdjustThreshMS;
	DWORD           dwMulticastPeriodMS;

	in_addr			iaMulticastAddress;

	SVSCookie		ckNextRefresh;
	SVSCookie		ckNextMulticast;

	union {
		struct {
			unsigned int fStarted           : 1;
			unsigned int fClientOnly        : 1;
			unsigned int fSystemTimeCorrect : 1;
			unsigned int fRefreshRequired   : 1;
			unsigned int fForceTimeToServer : 1;
		};
		unsigned int uiFlags;
	};

	unsigned char	sntp_server[DNS_MAX_NAME_BUFFER_LENGTH];

	void ReInit (void) {
		DEBUGMSG(ZONE_TRACE, (L"[TIMESVC] State reinitialized\r\n"));

		pEvents = 0;
		sServer = INVALID_SOCKET;
		llLastSyncTimeXX = 0;
		ckNextRefresh = ckNextMulticast = 0;

		iaMulticastAddress.S_un.S_addr = INADDR_ANY;

		dwRefreshMS = dwRecoveryRefreshMS = dwAdjustThreshMS = dwMulticastPeriodMS = 0;

		uiFlags = 0;

		memset (sntp_server, 0, sizeof(sntp_server));
	}

public:
	unsigned __int64		llLastSyncTimeXX;

	int IsStarted (void) { return fStarted; }
	int LastUpdateFailed (void) { return fRefreshRequired; }

	int RefreshConfig (void);
	int UpdateNowOrLater (enum When, int fForceTime = FALSE);
	int TimeChanged (void);

	int Start (void);
	SVSThreadPool *Stop (void);

	TimeState (void) {
		ReInit ();
	}

	friend DWORD WINAPI RxThread (LPVOID lpUnused);
	friend DWORD WINAPI TimeRefreshThread (LPVOID lpUnused);
	friend DWORD WINAPI MulticastThread (LPVOID lpUnused);
    //Added from DST.cpp
    friend BOOL  WINAPI DST_DetermineChangeDate(LPSYSTEMTIME pStTzChange);
    friend WORD  WINAPI GetFirstWeekDayOfMonth(LPSYSTEMTIME pst);
    friend BOOL  WINAPI DST_Auto(void);
    friend void  WINAPI DisableDSTNotifies();
};

///////////////////////////////////////////////////////////////////////
//
//		Base Classes - implementation
//
//
int TimeState::RefreshConfig (void) {
	DEBUGMSG(ZONE_INIT, (L"[TIMESVC] Refreshing configuration\r\n"));

	HKEY hk;
	int iErr = ERROR_BAD_CONFIGURATION;

	ReInit ();

	if (ERROR_SUCCESS == RegOpenKeyEx (HKEY_LOCAL_MACHINE, BASE_KEY, 0, KEY_READ, &hk)) {
		WCHAR szTimeServer[DNS_MAX_NAME_BUFFER_LENGTH];
		DWORD dwType = 0;
		DWORD dwSize = sizeof(szTimeServer) - sizeof(WCHAR);

		iErr = ERROR_SUCCESS;
		if (ERROR_SUCCESS == RegQueryValueEx (hk, L"server", NULL, &dwType, (LPBYTE)szTimeServer, &dwSize)) {
			if ((dwType != REG_SZ) || (dwSize >= sizeof(szTimeServer) - sizeof(WCHAR)) || ((dwSize & 1) != 0)) {
				RETAILMSG(1, (L"[TIMESVC] Configuration error: server name incorrect. Aborting.\r\n"));
				iErr = ERROR_BAD_CONFIGURATION;
			}

			szTimeServer[DNS_MAX_NAME_BUFFER_LENGTH-1] = '\0';
			if (wcslen (szTimeServer) == (DNS_MAX_NAME_BUFFER_LENGTH-1)) {
				RETAILMSG(1, (L"[TIMESVC] Configuration error: server name incorrect. Aborting.\r\n"));
				iErr = ERROR_BAD_CONFIGURATION;
			}

			if (iErr == ERROR_SUCCESS) {
				if (! WideCharToMultiByte (CP_ACP, 0, szTimeServer, -1, (char *)sntp_server, sizeof(sntp_server), NULL, NULL)) {
					RETAILMSG(1, (L"[TIMESVC] Configuration error: server name incorrect. Aborting.\r\n"));
					iErr = ERROR_BAD_CONFIGURATION;
				}
			}

			if (iErr == ERROR_SUCCESS) {
				iErr = ERROR_BAD_CONFIGURATION;	// Require refresh key if we have a client

				dwType = 0;
				dwSize = sizeof(dwRefreshMS);
				if ((ERROR_SUCCESS == RegQueryValueEx (hk, L"refresh", NULL, &dwType, (LPBYTE)&dwRefreshMS, &dwSize)) &&
					(dwType == REG_DWORD) && (dwSize == sizeof(DWORD)) && (dwRefreshMS >= MIN_REFRESH))
					iErr = ERROR_SUCCESS;
				else {
					RETAILMSG(1, (L"[TIMESVC] Configuration error: refresh rate incorrect. Aborting.\r\n"));
				}
			}

			if (iErr == ERROR_SUCCESS) {
				iErr = ERROR_BAD_CONFIGURATION;	// Require accelerated refresh key if we have a client

				dwType = 0;
				dwSize = sizeof(dwRecoveryRefreshMS);
				if ((ERROR_SUCCESS == RegQueryValueEx (hk, L"recoveryrefresh", NULL, &dwType, (LPBYTE)&dwRecoveryRefreshMS, &dwSize)) &&
					(dwType == REG_DWORD) && (dwSize == sizeof(DWORD)) && (dwRecoveryRefreshMS >= MIN_REFRESH) && (dwRecoveryRefreshMS <= dwRefreshMS))
					iErr = ERROR_SUCCESS;
				else {
					RETAILMSG(1, (L"[TIMESVC] Configuration error: accelerated refresh rate incorrect. Aborting.\r\n"));
				}
			}

			if (iErr == ERROR_SUCCESS) {
				iErr = ERROR_BAD_CONFIGURATION;	// Require threshold if we have a client

				DWORD dw;
				dwType = 0;
				dwSize = sizeof(dw);

				if ((ERROR_SUCCESS == RegQueryValueEx (hk, L"threshold", NULL, &dwType, (LPBYTE)&dw, &dwSize)) &&
					(dwType == REG_DWORD) && (dwSize == sizeof(dw))) {
					dwAdjustThreshMS = dw;

					iErr = ERROR_SUCCESS;
				} else {
					RETAILMSG(1, (L"[TIMESVC] Configuration error: time adjustment threshold incorrect. Aborting.\r\n"));
				}

			}
		}

		DWORD dw;
		dwType = 0;
		dwSize = sizeof(dw);

		if ((iErr == ERROR_SUCCESS) && (ERROR_SUCCESS == RegQueryValueEx (hk, L"clientonly", NULL, &dwType, (LPBYTE)&dw, &dwSize)) &&
							(dwType == REG_DWORD) && (dwSize == sizeof(dw)) && dw)
			fClientOnly = TRUE;

		if (fClientOnly && (sntp_server[0] == '\0'))
			iErr = ERROR_BAD_CONFIGURATION;

		dwType = 0;
		dwSize = sizeof(dw);

		if ((ERROR_SUCCESS == RegQueryValueEx (hk, L"trustlocalclock", NULL, &dwType, (LPBYTE)&dw, &dwSize)) &&
							(dwType == REG_DWORD) && (dwSize == sizeof(dw)) && dw)
			fSystemTimeCorrect = TRUE;

		if (sntp_server[0] == '\0')	// No client - system clock must be correct
			fSystemTimeCorrect = TRUE;

		if (! fClientOnly) {
			dwType = 0;
			dwSize = sizeof(dw);

			if ((ERROR_SUCCESS == RegQueryValueEx (hk, L"multicast", NULL, &dwType, (LPBYTE)&dw, &dwSize)) &&
							(dwType == REG_DWORD) && (dwSize == sizeof(dw)))
				iaMulticastAddress.S_un.S_addr = dw;

			if (iaMulticastAddress.S_un.S_addr != INADDR_ANY) {
				if ((ERROR_SUCCESS == RegQueryValueEx (hk, L"multicastperiod", NULL, &dwType, (LPBYTE)&dw, &dwSize)) &&
							(dwType == REG_DWORD) && (dwSize == sizeof(dw)) && (dw > MIN_MULTICAST))
					dwMulticastPeriodMS = dw;
				else {
					iErr = ERROR_BAD_CONFIGURATION;
					RETAILMSG(1, (L"[TIMESVC] Configuration error: Multicast period required. Aborting.\r\n"));
				}
			}
		}
		RegCloseKey (hk);
	} else {
		iErr = ERROR_SUCCESS;
		strcpy ((char *)sntp_server, "tock.usno.navy.mil");
		dwRefreshMS = 14*24*60*60*1000;			// Synchronize clock every two weeks
		dwRecoveryRefreshMS = 24*60*60*1000;	// Try daily if it fails
		dwAdjustThreshMS = 24*60*60*1000;				// Allow one day

		fSystemTimeCorrect = TRUE;				// Server provides time even if time synch failed

		iaMulticastAddress.S_un.S_addr = INADDR_BROADCAST;
		dwMulticastPeriodMS = 60*60*1000;	// Beat every hour
	}

	DEBUGMSG(ZONE_INIT, (L"[TIMESVC] Configuration: sntp server           : %S\r\n", sntp_server));
	DEBUGMSG(ZONE_INIT, (L"[TIMESVC] Configuration: regular refresh       : %d ms (%d day(s))\r\n", dwRefreshMS, dwRefreshMS/(24*60*60*1000)));
	DEBUGMSG(ZONE_INIT, (L"[TIMESVC] Configuration: accelerated refresh   : %d ms (%d day(s))\r\n", dwRecoveryRefreshMS, dwRefreshMS/(24*60*60*1000)));
	DEBUGMSG(ZONE_INIT, (L"[TIMESVC] Configuration: adjustment threshold  : %d ms\r\n", dwAdjustThreshMS));
	DEBUGMSG(ZONE_INIT, (L"[TIMESVC] Configuration: client-only operation : %s\r\n", fClientOnly ? L"yes" : L"no"));
	if (! fClientOnly) {
		DEBUGMSG(ZONE_INIT, (L"[TIMESVC] Configuration: system clock          : %s\r\n", fSystemTimeCorrect ? L"presumed correct" : L"presumed wrong if not updates"));
		DEBUGMSG(ZONE_INIT, (L"[TIMESVC] Configuration: multicast             : %S\r\n", inet_ntoa (iaMulticastAddress)));
		DEBUGMSG(ZONE_INIT, (L"[TIMESVC] Configuration: multicast period      : %d ms\r\n", dwMulticastPeriodMS));
	}

	return iErr;
}

int TimeState::UpdateNowOrLater (enum When when, int fForceTime) {
	if (sntp_server[0] == '\0') {
		DEBUGMSG(ZONE_CLIENT, (L"[TIMESVC] Client: not configured; update request ignored\r\n"));
		return ERROR_SUCCESS;
	}

	DWORD dwTimeout;
	if (when == Now) {
		fRefreshRequired = TRUE;
		dwTimeout = NULL;
	} else if (when == Shortly) {
		SVSUTIL_ASSERT (fRefreshRequired);
		dwTimeout = dwRecoveryRefreshMS;
	} else
		dwTimeout = dwRefreshMS;

	DEBUGMSG(ZONE_CLIENT, (L"[TIMESVC] Client: schedule time update in %d ms %s\r\n", dwTimeout, fForceTime ? L"(require refresh)" : L""));

	if (ckNextRefresh)
		pEvents->UnScheduleEvent (ckNextRefresh);

	if (fForceTime || (! fSystemTimeCorrect))
		fForceTimeToServer = TRUE;

	if (! (ckNextRefresh = pEvents->ScheduleEvent (TimeRefreshThread, NULL, dwTimeout)))
		return ERROR_OUTOFMEMORY;

	return ERROR_SUCCESS;
}

int TimeState::TimeChanged (void) {
	DEBUGMSG(ZONE_TRACE, (L"[TIMESVC] Server: Time changed - multicast now?\r\n"));
	if (iaMulticastAddress.S_un.S_addr == INADDR_ANY)
		return 0;

	if (ckNextMulticast)
		pEvents->UnScheduleEvent (ckNextMulticast);

	if (! (ckNextMulticast = pEvents->ScheduleEvent (MulticastThread, NULL, 0)))
		return ERROR_OUTOFMEMORY;

	return ERROR_SUCCESS;
}

int TimeState::Start (void) {
	DEBUGMSG(ZONE_INIT, (L"[TIMESVC] Service starting\r\n"));

	pEvents = new SVSThreadPool (5);
	if (! pEvents) {
		DEBUGMSG(ZONE_ERROR, (L"[TIMESVC] Start error: Out of memory allocating threads\r\n"));
		return ERROR_OUTOFMEMORY;
	}

	if (! fClientOnly) {
		SOCKADDR_IN sa;

		memset ((char *)&sa, 0, sizeof(sa));

		sa.sin_family = AF_INET;
		sa.sin_port = htons(NTP_PORT);
		sa.sin_addr.S_un.S_addr = 0;

		if ((INVALID_SOCKET == (sServer = socket (PF_INET, SOCK_DGRAM, 0))) ||
			(0 != bind(sServer, (SOCKADDR *)&sa, sizeof(sa)))) {
			int iErr = GetLastError ();
			if (iErr == ERROR_SUCCESS)
				iErr = ERROR_INTERNAL_ERROR;

			DEBUGMSG(ZONE_ERROR, (L"[TIMESVC] Start error: failure to register time server (error %d)\r\n", iErr));

			if (sServer != INVALID_SOCKET) {
				closesocket (sServer);
				sServer = INVALID_SOCKET;
			}

			delete pEvents;
			ReInit ();

			return iErr;
		}

		if ( ! pEvents->ScheduleEvent (RxThread, NULL)) {
			closesocket (sServer);
			SVSThreadPool *pp = pEvents;
			ReInit ();

			DEBUGMSG(ZONE_ERROR, (L"[TIMESVC] Start error: Out of memory scheduling server thread\r\n"));

			Unlock ();
			delete pp;
			Lock ();

			return ERROR_OUTOFMEMORY;
		}
	}

	if (ERROR_SUCCESS != UpdateNowOrLater (Now)) {
		if (sServer != INVALID_SOCKET)
			closesocket (sServer);

		SVSThreadPool *pp = pEvents;

		DEBUGMSG(ZONE_ERROR, (L"[TIMESVC] Start error: Out of memory scheduling time update\r\n"));

		ReInit ();

		Unlock ();
		delete pp;
		Lock ();

		return ERROR_OUTOFMEMORY;
	}

	fStarted = TRUE;

	DEBUGMSG(ZONE_INIT, (L"[TIMESVC] Service started successfully\r\n"));

	return ERROR_SUCCESS;
}

SVSThreadPool *TimeState::Stop (void) {
	DEBUGMSG(ZONE_INIT, (L"[TIMESVC] Stopping service\r\n"));
	fStarted = FALSE;

	if (sServer != INVALID_SOCKET)
		closesocket (sServer);

	sServer = INVALID_SOCKET;
	SVSThreadPool *pp = pEvents;
	pEvents = NULL;
	return pp;
}

///////////////////////////////////////////////////////////////////////
//
//		Globals
//
//
static TimeState *gpTS = NULL;

///////////////////////////////////////////////////////////////////////
//
//		Service functions
//
//	Note: NTP time conversion functions derived from XP's NTP sources (ntpbase.*)
//
static inline unsigned char EndianSwap (unsigned char x) {
	return x;
}

static inline unsigned short EndianSwap (unsigned short x) {
	return (EndianSwap ((unsigned char)x) << 8) | EndianSwap ((unsigned char)(x >> 8));
}

static inline unsigned int EndianSwap (unsigned int x) {
	return (EndianSwap ((unsigned short)x) << 16) | EndianSwap ((unsigned short)(x >> 16));
}

static inline unsigned __int64 EndianSwap (unsigned __int64 x) {
	return (((unsigned __int64)EndianSwap ((unsigned int)x)) << 32) | EndianSwap ((unsigned int)(x >> 32));
}

static unsigned __int64 NtTimeEpochFromNtpTimeEpoch(unsigned __int64 te) {
    //return (qwNtpTime*(10**7)/(2**32))+NTPTIMEOFFSET
    // ==>
    //return (qwNtpTime*( 5**7)/(2**25))+NTPTIMEOFFSET
    // ==>
    //return ((qwNTPtime*FIVETOTHESEVETH)>>25)+NTPTIMEOFFSET;  
    // ==>
    // Note: 'After' division, we round (instead of truncate) the result for better precision
    unsigned __int64 qwNtpTime=EndianSwap(te);
    unsigned __int64 qwTemp=((qwNtpTime&0x00000000FFFFFFFF)*FIVETOTHESEVETH)+0x0000000001000000; //rounding step: if 25th bit is set, round up;

	return (qwTemp>>25) + ((qwNtpTime&0xFFFFFFFF00000000)>>25)*FIVETOTHESEVETH + NTPTIMEOFFSET;
}

static unsigned __int64 NtpTimeEpochFromNtTimeEpoch(unsigned __int64 te) {
    //return (qwNtTime-NTPTIMEOFFSET)*(2**32)/(10**7);
    // ==>
    //return (qwNtTime-NTPTIMEOFFSET)*(2**25)/(5**7);
    // ==>
    //return ((qwNtTime-NTPTIMEOFFSET)<<25)/FIVETOTHESEVETH);
    // ==>
    // Note: The high bit is lost (and assumed to be zero) but 
    //       it will not be set for another 29,000 years (around year 31587). No big loss.
    // Note: 'After' division, we truncate the result because the precision of NTP already excessive
    unsigned __int64 qwTemp=(te-NTPTIMEOFFSET)<<1; 
    unsigned __int64 qwHigh=qwTemp>>8;
    unsigned __int64 qwLow=(qwHigh%FIVETOTHESEVETH)<<32 | (qwTemp&0x00000000000000FF)<<24;

	return EndianSwap(((qwHigh/FIVETOTHESEVETH)<<32) | (qwLow/FIVETOTHESEVETH));
}

static void GetCurrTimeNtp (unsigned __int64 *ptimeXX) {
	SYSTEMTIME st;
	GetSystemTime (&st);

	union {
		FILETIME ft;
		unsigned __int64 ui64ft;
	};

	SystemTimeToFileTime (&st, &ft);

	*ptimeXX = NtpTimeEpochFromNtTimeEpoch (ui64ft);
}

static int Exec (LPTHREAD_START_ROUTINE pfunc) {
	HANDLE h = CreateThread (NULL, 0, pfunc, NULL, 0, NULL);
	if (! h)
		return GetLastError ();

	WaitForSingleObject (h, INFINITE);

	DWORD dw = ERROR_INTERNAL_ERROR;
	GetExitCodeThread (h, &dw);
	CloseHandle (h);

	return (int)dw;
}

#if defined (DEBUG) || defined (_DEBUG)
static void DumpPacket (NTP_REQUEST *pPacket) {
	unsigned int li = pPacket->li ();
	unsigned int vn = pPacket->vn ();
	unsigned int mode = pPacket->mode ();
	unsigned int poll = pPacket->poll ();
	unsigned int stratum = pPacket->stratum ();
	unsigned int precision = pPacket->precision ();
	unsigned int root_delay = pPacket->root_delay ();
	unsigned int root_dispersion = pPacket->root_dispersion ();
	unsigned int ref_id = pPacket->ref_id ();
	unsigned __int64 ref_stamp = pPacket->ref_stamp ();
	unsigned __int64 orig_stamp = pPacket->orig_stamp ();
	unsigned __int64 recv_stamp = pPacket->recv_stamp ();
	unsigned __int64 trans_stamp = pPacket->trans_stamp ();

	DEBUGMSG(ZONE_PACKETS, (L"\r\nSNTP Packet:\r\n"));

	WCHAR *pText = L"";

	switch (li) {
		case 0:
			pText = L"No warning";
			break;
		case 1:
			pText = L"Last minute has 61 seconds";
			break;
		case 2:
			pText = L"Last minute has 59 seconds";
			break;
		case 3:
			pText = L"Alarm condition (clock not synchronized)";
			break;
		default:
			pText = L"Illegal or reserved code";
			break;
	}

	DEBUGMSG(ZONE_PACKETS, (L"Leap      : %d (%s)\r\n", li, pText));
	DEBUGMSG(ZONE_PACKETS, (L"Version   : %d\r\n", vn));

	pText = L"";
	switch (mode) {
		case 1:
			pText = L"symmetric active";
			break;
		case 2:
			pText = L"symmetric passive";
			break;
		case 3:
			pText = L"client";
			break;
		case 4:
			pText = L"server";
			break;
		case 5:
			pText = L"broadcast";
			break;
		case 6:
			pText = L"NTP control";
			break;
		case 7:
			pText = L"private use";
			break;
		default:
			pText = L"illegal or reserved code";
			break;
	}

	DEBUGMSG(ZONE_PACKETS, (L"Mode      : %d (%s)\r\n", mode, pText));
	DEBUGMSG(ZONE_PACKETS, (L"Stratum   : %d\r\n", stratum));
	DEBUGMSG(ZONE_PACKETS, (L"Poll      : %d\r\n", poll));
	DEBUGMSG(ZONE_PACKETS, (L"Precision : %d\r\n", poll));
	DEBUGMSG(ZONE_PACKETS, (L"Root delay: 0x%08x (%d sec)\r\n", root_delay, ((int)root_delay)/4));
	DEBUGMSG(ZONE_PACKETS, (L"Root disp : 0x%08x (%d sec)\r\n", root_dispersion, ((int)root_dispersion)/4));
	in_addr ia;
	ia.S_un.S_addr = ref_id;
	DEBUGMSG(ZONE_PACKETS, (L"Refid     : %08x (or %S)\r\n", ref_id, inet_ntoa (ia)));

	union {
		unsigned __int64 ui64;
		FILETIME ft;
	};

	SYSTEMTIME xst;

	ui64 = NtTimeEpochFromNtpTimeEpoch (ref_stamp);
	FileTimeToSystemTime ((FILETIME *)&ui64, &xst);
	DEBUGMSG(ZONE_PACKETS, (L"Reference time: %02d/%02d/%d %02d:%02d:%02d.%03d\n", xst.wMonth, xst.wDay, xst.wYear, xst.wHour, xst.wMinute, xst.wSecond, xst.wMilliseconds));

	ui64 = NtTimeEpochFromNtpTimeEpoch (orig_stamp);
	FileTimeToSystemTime ((FILETIME *)&ui64, &xst);
	DEBUGMSG(ZONE_PACKETS, (L"Origination time: %02d/%02d/%d %02d:%02d:%02d.%03d\n", xst.wMonth, xst.wDay, xst.wYear, xst.wHour, xst.wMinute, xst.wSecond, xst.wMilliseconds));

	ui64 = NtTimeEpochFromNtpTimeEpoch (recv_stamp);
	FileTimeToSystemTime ((FILETIME *)&ui64, &xst);
	DEBUGMSG(ZONE_PACKETS, (L"Received time: %02d/%02d/%d %02d:%02d:%02d.%03d\n", xst.wMonth, xst.wDay, xst.wYear, xst.wHour, xst.wMinute, xst.wSecond, xst.wMilliseconds));

	ui64 = NtTimeEpochFromNtpTimeEpoch (trans_stamp);
	FileTimeToSystemTime ((FILETIME *)&ui64, &xst);
	DEBUGMSG(ZONE_PACKETS, (L"Transmitted time: %02d/%02d/%d %02d:%02d:%02d.%03d\n", xst.wMonth, xst.wDay, xst.wYear, xst.wHour, xst.wMinute, xst.wSecond, xst.wMilliseconds));
	DEBUGMSG(ZONE_PACKETS, (L"\r\n\r\n"));
}
#endif

///////////////////////////////////////////////////////////////////////
//
//		Threads
//
//
static DWORD WINAPI MulticastThread (LPVOID lpUnused) {
	DEBUGMSG(ZONE_SERVER, (L"[TIMESVC] Multicast Event\r\n"));
	int fSuccess = FALSE;

	if (! gpTS) {
		DEBUGMSG(ZONE_ERROR, (L"[TIMESVC] Multicast: service not initialized!\r\n"));
		return 0;
	}

	gpTS->Lock ();

	if (! gpTS->IsStarted ()) {
		DEBUGMSG(ZONE_ERROR, (L"[TIMESVC] Time Refresh: service not active!\r\n"));
		gpTS->Unlock ();

		return 0;
	}

	int fTimeAccurate = gpTS->fSystemTimeCorrect || (! gpTS->fRefreshRequired);

	in_addr ia = gpTS->iaMulticastAddress;
	if ((ia.S_un.S_addr != INADDR_ANY) && (gpTS->dwMulticastPeriodMS > 1000))
		gpTS->ckNextMulticast = gpTS->pEvents->ScheduleEvent (MulticastThread, NULL, gpTS->dwMulticastPeriodMS);

	unsigned int uiPollInterval = gpTS->dwMulticastPeriodMS / 1000;
	unsigned int poll = 1;

	while (poll < uiPollInterval)
		poll <<= 1;
	poll >>= 1;

	unsigned int ref = (unsigned int)gpTS->llLastSyncTimeXX & 0xffffffff;
	unsigned __int64 ref_ts = gpTS->llLastSyncTimeXX;

	gpTS->Unlock ();

	if (ia.S_un.S_addr == INADDR_ANY) {
		DEBUGMSG(ZONE_SERVER, (L"[TIMESVC] Multicast server disabled\r\n"));
		return 0;
	}

	if (! fTimeAccurate) {
		DEBUGMSG(ZONE_SERVER, (L"[TIMESVC] Multicast server does not have accurate time\r\n"));
		return 0;
	}

	SOCKET s = socket (PF_INET, SOCK_DGRAM, 0);

	if (s == INVALID_SOCKET) {
		DEBUGMSG(ZONE_ERROR, (L"[TIMESVC] Multicast failed: cannot allocate socket (error %d). Aborting.\n", GetLastError ()));
		return 0;
	}

	int on = 1;
	if (0 != setsockopt (s, SOL_SOCKET, SO_BROADCAST, (char *)&on, sizeof(on))) {
		DEBUGMSG(ZONE_ERROR, (L"[TIMESVC] Multicast failed: error %d\n", GetLastError ()));
	}

	SOCKADDR_IN sa;
	memset (&sa, 0, sizeof(sa));

	sa.sin_port   = htons (NTP_PORT);
	sa.sin_family = AF_INET;
	sa.sin_addr   = ia;

	NTP_REQUEST dg;
	memset (&dg, 0, sizeof(dg));
	dg.set_vn (4);
	dg.set_mode (5);
	dg.set_stratum (2);
	dg.set_poll (poll);
	dg.set_precision((unsigned int)-7);
	dg.set_ref_id (ref);
	dg.set_ref_stamp (ref_ts);

	unsigned __int64 llTXX;
	GetCurrTimeNtp (&llTXX);

	dg.set_trans_stamp (llTXX);

	if (sizeof(dg) != sendto (s, (char *)&dg, sizeof(dg), 0, (sockaddr *)&sa, sizeof(sa))) {
		DEBUGMSG(ZONE_ERROR, (L"[TIMESVC] Multicast failed: error %d\n", GetLastError ()));
	}

	closesocket (s);

	DEBUGMSG(ZONE_SERVER, (L"[TIMESVC] Multicast event processing completed\r\n"));
	return 0;
}

static DWORD WINAPI TimeRefreshThread (LPVOID lpUnused) {
	DEBUGMSG(ZONE_CLIENT, (L"[TIMESVC] Refresh Event\r\n"));
	int fSuccess = FALSE;
	int fTimeChanged = FALSE;

	if (! gpTS) {
		DEBUGMSG(ZONE_ERROR, (L"[TIMESVC] Time Refresh: service not initialized!\r\n"));
		return 0;
	}

	gpTS->Lock ();

	if (! gpTS->IsStarted ()) {
		DEBUGMSG(ZONE_ERROR, (L"[TIMESVC] Time Refresh: service not active!\r\n"));
		gpTS->Unlock ();

		return 0;
	}

	int fForceTime = gpTS->fForceTimeToServer;
	DWORD dwMaxAdjustS = gpTS->dwAdjustThreshMS / 1000;

	char hostname[DNS_MAX_NAME_BUFFER_LENGTH];
	memcpy (hostname, gpTS->sntp_server, sizeof(hostname));

	gpTS->Unlock ();

	unsigned __int64 llT1XX, llT4XX;

	for (int i = 0 ; (i < 3) && (! fSuccess) ; ++i) {
		NTP_REQUEST dg;
		memset (&dg, 0, sizeof(dg));
		dg.set_vn (4);
		dg.set_mode (3);

		hostent *he = gethostbyname (hostname);

		if (he) {
			DEBUGMSG(ZONE_CLIENT, (L"[TIMESVC] Time Refresh: querying server %S (%S)\r\n", hostname, inet_ntoa(*(in_addr *)he->h_addr_list[0])));

			SOCKET s = socket (PF_INET, SOCK_DGRAM, 0);

			SOCKADDR_IN sa;
			memset (&sa, 0, sizeof(sa));

			sa.sin_port             = htons (NTP_PORT);
			sa.sin_family           = AF_INET;
			sa.sin_addr.S_un.S_addr = *(unsigned long *)he->h_addr_list[0];

			GetCurrTimeNtp (&llT1XX);

			dg.set_trans_stamp (llT1XX);

#if defined (DEBUG) || defined (_DEBUG)
			DEBUGMSG (ZONE_PACKETS, (L"[TIMESVC] Sending SNTP request\r\n"));
			DumpPacket (&dg);
#endif

			if (sizeof (dg) == sendto (s, (char *)&dg, sizeof(dg), 0, (sockaddr *)&sa, sizeof(sa))) {
				DEBUGMSG(ZONE_CLIENT, (L"[TIMESVC] Time Refresh: sent request, awaiting response\r\n"));
				fd_set f;
				FD_ZERO (&f);
				FD_SET (s, &f);

				timeval tv;
				tv.tv_sec  = 3;
				tv.tv_usec = 0;

				if (select (0, &f, NULL, NULL, &tv) > 0) {
					int salen = sizeof(sa);
					if (sizeof(dg) == recvfrom (s, (char *)&dg, sizeof(dg), 0, (sockaddr *)&sa, &salen)) {
						GetCurrTimeNtp (&llT4XX);

#if defined (DEBUG) || defined (_DEBUG)
						DEBUGMSG (ZONE_PACKETS, (L"[TIMESVC] Received SNTP response\r\n"));
						DumpPacket (&dg);
#endif

						if ((dg.li () != 3) && (llT1XX == dg.orig_stamp ())) {
							unsigned __int64 llT2XX = dg.recv_stamp ();
							unsigned __int64 llT3XX = dg.trans_stamp ();

							unsigned __int64 llT1 = NtTimeEpochFromNtpTimeEpoch (llT1XX);
							unsigned __int64 llT2 = NtTimeEpochFromNtpTimeEpoch (llT2XX);
							unsigned __int64 llT3 = NtTimeEpochFromNtpTimeEpoch (llT3XX);
							unsigned __int64 llT4 = NtTimeEpochFromNtpTimeEpoch (llT4XX);

							__int64 llOffset = ((__int64)((llT2 - llT1) + (llT3 - llT4))) / 2;

							int iOffsetSec = (int)(llOffset / 10000000);
							if (fForceTime || ((DWORD)abs (iOffsetSec) < dwMaxAdjustS)) {

								BOOL fIsAutoDstEnabled = DST_Auto();

								SYSTEMTIME st, st2;
								unsigned __int64 llTime;
        							GetSystemTime (&st);
								SystemTimeToFileTime (&st, (FILETIME *)&llTime);
								llTime += llOffset;
                        
								FileTimeToSystemTime ((FILETIME *)&llTime, &st2); 

								// The auto-DST notification thread lives in explorer.exe 
								// and there is no clean IPC mechanism here.  So when
								// trying to update clock, DST|STD notifications, etc 
								// we get into horrible race conditions.  Way to fix this is 
								// to temporarily disable explorer's auto DST recalculation
								// while performing timsvc logic.
								
								if (fIsAutoDstEnabled) {
									DisableDSTNotifies();
								}

								SetDstOrStd(fIsAutoDstEnabled,&st2);
								SetSystemTime(&st2);

								if (fIsAutoDstEnabled) {
									RefreshExplorerDST();
								}

								fSuccess = TRUE;

								DEBUGMSG(ZONE_CLIENT, (L"[TIMESVC] Time Refresh: time accepted. offset = %d s.\r\n", iOffsetSec));
								DEBUGMSG(ZONE_CLIENT, (L"[TIMESVC] Time Refresh: old time: %02d/%02d/%d %02d:%02d:%02d.%03d\r\n", st.wMonth, st.wDay, st.wYear, st.wHour, st.wMinute, st.wSecond, st.wMilliseconds));
								DEBUGMSG(ZONE_CLIENT, (L"[TIMESVC] Time Refresh: new time: %02d/%02d/%d %02d:%02d:%02d.%03d\r\n", st2.wMonth, st2.wDay, st2.wYear, st2.wHour, st2.wMinute, st2.wSecond, st2.wMilliseconds));
 
								if (((DWORD)abs (iOffsetSec) > MIN_TIMEUPDATE) || (gpTS->ckNextMulticast == 0))
									fTimeChanged = TRUE;
							} else {
								DEBUGMSG(ZONE_ERROR, (L"[TIMESVC] Time Refresh: time indicated by server is not within allowed adjustment boundaries (offset = %d s)\r\n", iOffsetSec));
								closesocket (s);
								break;
							}
						} else {
							DEBUGMSG(ZONE_ERROR, (L"[TIMESVC] Time Refresh: sntp server not synchronized\r\n"));
							closesocket (s);
							break;
						}
					} else {
						DEBUGMSG(ZONE_ERROR, (L"[TIMESVC] Time Refresh: sntp server datagram size incorrect (or authentication requested)\r\n"));
					}
				} else {
					DEBUGMSG(ZONE_ERROR, (L"[TIMESVC] Time Refresh: sntp server response timeout (no SNTP on server?)\r\n"));
				}
			} else {
				DEBUGMSG(ZONE_ERROR, (L"[TIMESVC] Time Refresh: host %S unreachable\r\n", hostname));
			}

			closesocket (s);
		} else {
			DEBUGMSG(ZONE_ERROR, (L"[TIMESVC] Time Refresh: host %S unreachable\r\n", hostname));
		}
	}

	if (gpTS) {
		gpTS->Lock ();

		if (gpTS->IsStarted ()) {
			if (fSuccess) {
				gpTS->llLastSyncTimeXX = llT4XX;
				gpTS->fRefreshRequired = FALSE;

				gpTS->UpdateNowOrLater (Later);

				gpTS->fForceTimeToServer = FALSE;

				DEBUGMSG(ZONE_CLIENT, (L"[TIMESVC] Time successfully refreshed\r\n"));

				if (fTimeChanged)
					gpTS->TimeChanged ();
			} else {
				DEBUGMSG(ZONE_ERROR, (L"[TIMESVC] Time Refresh failed, rescheduling\r\n"));
				gpTS->fRefreshRequired = TRUE;
				gpTS->UpdateNowOrLater (Shortly);
			}
		}

		gpTS->Unlock ();
	}

	DEBUGMSG(ZONE_CLIENT, (L"[TIMESVC] Time Refresh event processing completed\r\n"));
	return 0;
}

static DWORD WINAPI RxThread (LPVOID lpUnused) {
	DEBUGMSG(ZONE_INIT, (L"[TIMESVC] Server thread started\r\n"));

	for ( ; ; ) {
		if (! gpTS) {
			DEBUGMSG(ZONE_ERROR, (L"[TIMESVC] Server: Not initialized, quitting\r\n"));
			return 0;
		}

		gpTS->Lock ();

		if (! gpTS->IsStarted ()) {
			DEBUGMSG(ZONE_ERROR, (L"[TIMESVC] Server: Not active, quitting\r\n"));
			gpTS->Unlock ();

			return 0;
		}

		SOCKET s = gpTS->sServer;
		gpTS->Unlock ();

		NTP_REQUEST	dg;

		SOCKADDR_IN sa;
        int salen = sizeof(sa);
        int iRecv = recvfrom (s, (char *)&dg, sizeof(dg), 0, (sockaddr *)&sa, &salen);

		if (iRecv <= 0) {
			DEBUGMSG(ZONE_ERROR, (L"[TIMESVC] Server: receive failed, error=%d\r\n", GetLastError ()));
			break;
		}

		if (iRecv != sizeof(dg)) {
			DEBUGMSG(ZONE_SERVER, (L"[TIMESVC] Server: size mismatch, packet ignored\r\n"));
			continue;
		}

#if defined (DEBUG) || defined (_DEBUG)
		DEBUGMSG(ZONE_PACKETS, (L"[TIMESVC] Received SNTP request\r\n"));
		DumpPacket (&dg);
#endif

		if (dg.mode () == 3) {	//client
			dg.set_mode (4);	//server
		} else if (dg.mode () == 1) {	// symmetric, active
			dg.set_mode(1);				// symmetric, passive
		} else {
			DEBUGMSG(ZONE_SERVER, (L"[TIMESVC] Server: Not a request, aborting\r\n"));
			continue;
		}

		dg.set_root_delay (0);
		dg.set_root_dispersion (0);
		dg.set_precision((unsigned int)-7);

		unsigned __int64 llOrigTimeXX = dg.trans_stamp ();
		dg.set_orig_stamp (llOrigTimeXX);

		unsigned __int64 llRecvTimeXX;
		GetCurrTimeNtp (&llRecvTimeXX);

		dg.set_recv_stamp (llRecvTimeXX);
		dg.set_trans_stamp (llRecvTimeXX);

		if (! gpTS)
			break;

		gpTS->Lock ();
		dg.set_ref_stamp (gpTS->llLastSyncTimeXX);
		dg.set_ref_id ((unsigned int)gpTS->llLastSyncTimeXX);

		if (gpTS->fSystemTimeCorrect || (! gpTS->fRefreshRequired)) {
			dg.set_li (0);
			dg.set_stratum (2);
		} else {
			dg.set_li (3);
			dg.set_stratum (15);
		}
		gpTS->Unlock ();

		sendto (s, (char *)&dg, sizeof(dg), 0, (sockaddr *)&sa, salen);

#if defined (DEBUG) || defined (_DEBUG)
		DEBUGMSG(ZONE_PACKETS, (L"[TIMESVC] Sent SNTP response\r\n"));
		DumpPacket (&dg);
#endif
	}

	DEBUGMSG(ZONE_INIT, (L"[TIMESVC] Server Thread exited\r\n"));
	return 0;
}

///////////////////////////////////////////////////////////////////////
//
//		Interface, initialization, management
//
//
static int InitializeFirstTime (HINSTANCE hMod) {
	svsutil_Initialize ();
	DEBUGREGISTER(hMod);

	DEBUGMSG(ZONE_INIT, (L"[TIMESVC] Module loaded\r\n"));

	gpTS = new TimeState;
	return (gpTS != NULL) ? ERROR_SUCCESS : ERROR_OUTOFMEMORY;
}

static int DestroyState (void) {
    delete gpTS;
	gpTS = NULL;

	DEBUGMSG(ZONE_INIT, (L"[TIMESVC] Module unloaded\r\n"));

	svsutil_DeInitialize ();

	return ERROR_SUCCESS;
}

static DWORD WINAPI SyncTime (LPVOID dwUnused) {
	DEBUGMSG(ZONE_TRACE, (L"[TIMESVC] Time update request from OS\r\n"));

	int iErr = ERROR_SERVICE_DOES_NOT_EXIST;
	if (gpTS) {
		gpTS->Lock ();
		iErr = ERROR_SUCCESS;

		if (! gpTS->IsStarted())
			iErr = ERROR_SERVICE_NOT_ACTIVE;

		if (iErr == ERROR_SUCCESS)
			iErr = gpTS->UpdateNowOrLater (Now);

		gpTS->Unlock ();
	}

	DEBUGMSG(ZONE_TRACE, (L"[TIMESVC] Time update request : %d\r\n", iErr));

	return iErr;
}

static DWORD WINAPI SetTime (LPVOID dwUnused) {
	DEBUGMSG(ZONE_TRACE, (L"[TIMESVC] Force time update request from OS\r\n"));

	int iErr = ERROR_SERVICE_DOES_NOT_EXIST;
	if (gpTS) {
		gpTS->Lock ();
		iErr = ERROR_SUCCESS;

		if (! gpTS->IsStarted())
			iErr = ERROR_SERVICE_NOT_ACTIVE;

		if (iErr == ERROR_SUCCESS)
			iErr = gpTS->UpdateNowOrLater (Now, TRUE);

		gpTS->Unlock ();
	}

	DEBUGMSG(ZONE_TRACE, (L"[TIMESVC] Force time update request : %d\r\n", iErr));

	return iErr;
}

static DWORD WINAPI NetworkChange (LPVOID dwUnused) {
	DEBUGMSG(ZONE_TRACE, (L"[TIMESVC] Network up/down event from OS\r\n"));

	int iErr = ERROR_SERVICE_DOES_NOT_EXIST;
	if (gpTS) {
		gpTS->Lock ();
		iErr = ERROR_SUCCESS;

		if (! gpTS->IsStarted())
			iErr = ERROR_SERVICE_NOT_ACTIVE;

		if ((iErr == ERROR_SUCCESS) && (gpTS->LastUpdateFailed ()))
			iErr = gpTS->UpdateNowOrLater (Now);

		gpTS->Unlock ();
	}

	DEBUGMSG(ZONE_TRACE, (L"[TIMESVC] Time update request : %d\r\n", iErr));

	return iErr;
}

static DWORD WINAPI StartServer (LPVOID lpUnused) {
	DEBUGMSG(ZONE_TRACE, (L"[TIMESVC] Start Server request from OS\r\n"));

	int iErr = ERROR_SERVICE_DOES_NOT_EXIST;
	if (gpTS) {
		gpTS->Lock ();
		iErr = ERROR_SUCCESS;

		if (gpTS->IsStarted())
			iErr = ERROR_ALREADY_INITIALIZED;

		WSADATA wsd;
		WSAStartup (MAKEWORD(1,1), &wsd);

		if (iErr == ERROR_SUCCESS)
			iErr = gpTS->RefreshConfig ();
	
		if (iErr == ERROR_SUCCESS)
			iErr = gpTS->Start ();

		gpTS->Unlock ();
	}

	DEBUGMSG(ZONE_TRACE, (L"[TIMESVC] Start Server request : %d\r\n", iErr));

	return iErr;
}

static DWORD WINAPI StopServer (LPVOID lpUnused) {
	DEBUGMSG(ZONE_TRACE, (L"[TIMESVC] Stop Server request from OS\r\n"));

	int iErr = ERROR_SERVICE_DOES_NOT_EXIST;
	if (gpTS) {
		gpTS->Lock ();
		iErr = ERROR_SUCCESS;

		if (! gpTS->IsStarted())
			iErr = ERROR_SERVICE_NOT_ACTIVE;

		SVSThreadPool *pp = (iErr == ERROR_SUCCESS) ? gpTS->Stop () : NULL;

		gpTS->Unlock ();

		if (pp) {
			delete pp;
			WSACleanup ();
		}
	}

	DEBUGMSG(ZONE_TRACE, (L"[TIMESVC] Stop Server request : %d\r\n", iErr));

	return iErr;
}

static DWORD WINAPI ExternalTimeUpdate (LPVOID lpUnused) {
	DEBUGMSG(ZONE_TRACE, (L"[TIMESVC] External time update indicator from OS\r\n"));

	int iErr = ERROR_SERVICE_DOES_NOT_EXIST;
	if (gpTS) {
		gpTS->Lock ();
		iErr = ERROR_SUCCESS;

		if (! gpTS->IsStarted())
			iErr = ERROR_SERVICE_NOT_ACTIVE;

		iErr = gpTS->TimeChanged ();

		gpTS->Unlock ();
	}

	DEBUGMSG(ZONE_TRACE, (L"[TIMESVC] External time update indicator : %d\r\n", iErr));

	return iErr;
}

///////////////////////////////////////////////////////////////////////
//
//		OS interface
//
//
#if defined (UNDER_CE)
#include <service.h>
#include <notify.h>

static HANDLE hNotifyThread = NULL;
static HANDLE hExitEvent = NULL;

typedef BOOL (*tCeRunAppAtEvent)(WCHAR *pwszAppName, LONG lWhichEvent);
typedef DWORD (*tNotifyAddrChange)(PHANDLE Handle, LPOVERLAPPED overlapped);

static DWORD WINAPI NotifyThread (LPVOID lpUnused) {
	DEBUGMSG(ZONE_INIT, (L"[TIMESVC] NotifyThread started\r\n"));

	HMODULE hCoreDll = LoadLibrary (L"coredll.dll");
	tCeRunAppAtEvent pCeRunAppAtEvent = (tCeRunAppAtEvent)GetProcAddress (hCoreDll, L"CeRunAppAtEvent");
	HANDLE hWakeup  = CreateEvent (NULL, FALSE, FALSE, L"timesvc\\wakeup");
	HANDLE hTimeSet = CreateEvent (NULL, FALSE, FALSE, L"timesvc\\timeset");
	if (! (pCeRunAppAtEvent && hWakeup && hTimeSet)) {
		DEBUGMSG(ZONE_ERROR, (L"[TIMESVC] NotifyThread: could not initialize, aborting\r\n"));

		FreeLibrary (hCoreDll);
		if (hWakeup)
			CloseHandle (hWakeup);

		if (hTimeSet)
			CloseHandle (hTimeSet);

		return 0;
	}

	// Clean up...
	pCeRunAppAtEvent (NAMED_EVENT_PREFIX_TEXT L"timesvc\\wakeup", 0);
	pCeRunAppAtEvent (NAMED_EVENT_PREFIX_TEXT L"timesvc\\timeset", 0);

	pCeRunAppAtEvent (NAMED_EVENT_PREFIX_TEXT L"timesvc\\wakeup", NOTIFICATION_EVENT_WAKEUP);
	pCeRunAppAtEvent (NAMED_EVENT_PREFIX_TEXT L"timesvc\\timeset", NOTIFICATION_EVENT_TIME_CHANGE);

	HMODULE hiphlp = LoadLibrary (L"iphlpapi.dll");
	tNotifyAddrChange pNotifyAddrChange = hiphlp ? (tNotifyAddrChange)GetProcAddress (hiphlp, L"NotifyAddrChange") : NULL;
	HANDLE hNetwork = NULL;
	if (pNotifyAddrChange)
		pNotifyAddrChange(&hNetwork, NULL);

	for ( ; ; ) {
		HANDLE ah[4];
		ah[0] = hExitEvent;
		ah[1] = hWakeup;
		ah[2] = hTimeSet;
		ah[3] = hNetwork;

		DWORD cEvents = hNetwork ? 4 : 3;

		DWORD dwRes = WaitForMultipleObjects (cEvents, ah, FALSE, INFINITE);

		if (dwRes == (WAIT_OBJECT_0+1))		// Wakeup
			Exec (SyncTime);
		else if (dwRes == (WAIT_OBJECT_0+2)) // Time reset
			Exec (ExternalTimeUpdate);
		else if (dwRes == (WAIT_OBJECT_0+3)) // Network address changed
			Exec (NetworkChange);
		else
			break;
	}

	pCeRunAppAtEvent (NAMED_EVENT_PREFIX_TEXT L"timesvc\\wakeup", 0);
	pCeRunAppAtEvent (NAMED_EVENT_PREFIX_TEXT L"timesvc\\timeset", 0);
	CloseHandle (hWakeup);
	CloseHandle (hTimeSet);

	if (hiphlp)
		FreeLibrary (hiphlp);

	FreeLibrary (hCoreDll);

	DEBUGMSG(ZONE_INIT, (L"Notify Thread exited\r\n"));

	return 0;
}

static int StartOsNotifications (void) {
	if (hExitEvent)
		return FALSE;

	hExitEvent = CreateEvent (NULL, TRUE, FALSE, NULL);
	hNotifyThread = NULL;

	if (hExitEvent) {
		hNotifyThread = CreateThread (NULL, 0, NotifyThread, NULL, 0, NULL);

		if (! hNotifyThread) {
			CloseHandle (hExitEvent);
			hExitEvent = NULL;
		}
	}

	return hNotifyThread != NULL;
}

static int StopOsNotifications (void) {
	if (hNotifyThread) {
		SetEvent (hExitEvent);
		CloseHandle (hExitEvent);
		hExitEvent = NULL;

		WaitForSingleObject (hNotifyThread, INFINITE);
		CloseHandle (hNotifyThread);
		hNotifyThread = NULL;
	}

	return TRUE;
}

DWORD WINAPI DllEntry (HINSTANCE hinstDLL, DWORD   Op, LPVOID  lpvReserved) {
	switch (Op) {
	case DLL_PROCESS_ATTACH :
		DisableThreadLibraryCalls((HMODULE)hinstDLL);
		if (ERROR_SUCCESS != InitializeFirstTime (hinstDLL))
			return FALSE;

		break;

	case DLL_PROCESS_DETACH :
		DestroyState ();
		break;
	}
	return TRUE;
}

//  @func PVOID | NTP_Init | Service initialization routine
//  @parm DWORD | dwData | Info passed to RegisterDevice
//  @rdesc      Returns a DWORD which will be passed to Open & Deinit or NULL if
//              unable to initialize the device.
//
//  This is called only once. Do the startup initialization in a thread
//  spawned by this function, but DO NOT BLOCK HERE!
//        
extern "C" DWORD NTP_Init (DWORD dwData) {
	DEBUGMSG(ZONE_TRACE, (L"[TIMESVC] NTP_Init\r\n"));

	if (! (dwData & SERVICE_INIT_STOPPED)) {
		int iErr = Exec (StartServer);
		if (iErr == ERROR_SUCCESS)
			StartOsNotifications ();

		return iErr == ERROR_SUCCESS;
	}

    return TRUE;
}

//  @func PVOID | NTP_Deinit | Device deinitialization routine
//  @parm DWORD | dwData | value returned from HLO_Init call
//  @rdesc	Returns TRUE for success, FALSE for failure.
//
//  The library WILL BE UNLOADED after this. Block here
//  until the state is completely clear and all the
//  threads are gone.
//
extern "C" BOOL NTP_Deinit(DWORD dwData) {
	DEBUGMSG(ZONE_TRACE, (L"[TIMESVC] NTP_Deinit\r\n"));

	Exec (StopServer);
	StopOsNotifications ();

    return TRUE;
}

//  @func PVOID | NTP_Open     | Device open routine
//  @parm DWORD | dwData       | value returned from HLO_Init call
//  @parm DWORD | dwAccess     | requested access (combination of GENERIC_READ
//                               and GENERIC_WRITE)
//  @parm DWORD | dwShareMode | requested share mode (combination of
//                               FILE_SHARE_READ and FILE_SHARE_WRITE)
//  @rdesc	Returns a DWORD which will be passed to Read, Write, etc or NULL if
//                               unable to open device.
//
//  We don't do anything here, but in a real service this is a place to create
//  client process state. HCO_Close will be called with this handle when the process
//  exits or terminates, so the clean-up is easy.
//
extern "C" DWORD NTP_Open (DWORD dwData, DWORD dwAccess, DWORD dwShareMode) {
    return TRUE;
}

//  @func BOOL  | NTP_Close | Device close routine
//  @parm DWORD | dwOpenData | value returned from HLO_Open call
//  @rdesc      Returns TRUE for success, FALSE for failure
//
//  Clean-up the client process state here.
//
extern "C" BOOL NTP_Close (DWORD dwData)  {
    return TRUE;
}

//  @func DWORD   | NTP_Write  | Device write routine
//  @parm DWORD   | dwOpenData | value returned from HLO_Open call
//  @parm LPCVOID | pBuf     | buffer containing data
//  @parm DWORD   | len        | maximum length to write [IN BYTES, NOT WORDS!!!]
//  @rdesc           Returns -1 for error, otherwise the number of bytes written.  The
//                   length returned is guaranteed to be the length requested unless an
//                   error condition occurs.
//
//  This is a vestige of streaming driver interface. We don't use it for services.
//
extern "C" DWORD NTP_Write (DWORD dwData, LPCVOID pInBuf, DWORD dwInLen) {
    return -1;
}

//  @func DWORD  | NTP_Read   | Device read routine
//  @parm DWORD  | dwOpenData | value returned from HLO_Open call
//  @parm LPVOID | pBuf       | buffer to receive data
//  @parm DWORD  | len        | maximum length to read [IN BYTES, not WORDS!!]
//  @rdesc      Returns 0 for end of file, -1 for error, otherwise the number of
//              bytes read.  The length returned is guaranteed to be the length
//              requested unless end of file or an error condition occurs.
//
//  This is a vestige of streaming driver interface. We don't use it for services.
//
extern "C" DWORD NTP_Read (DWORD dwData, LPVOID pBuf, DWORD dwLen) {
    return -1;
}

//  @func DWORD | NTP_Seek   | Device seek routine
//  @parm DWORD | dwOpenData | value returned from HLO_Open call
//  @parm long  | pos        | position to seek to (relative to type)
//  @parm DWORD | type       | FILE_BEGIN, FILE_CURRENT, or FILE_END
//  @rdesc      Returns current position relative to start of file, or -1 on error
//
//  This is a vestige of streaming driver interface. We don't use it for services.
//
extern "C" DWORD NTP_Seek (DWORD dwData, long pos, DWORD type) {
    return (DWORD)-1;
}

//  @func void | NTP_PowerUp | Device powerup routine
//  @comm       Called to restore device from suspend mode. 
//              You cannot call any routines aside from those in your dll in this call.
//
//  This is a vestige of streaming driver interface. We don't use it for services.
//
extern "C" void NTP_PowerUp(void) {
	return;
}

//  @func void | NTP_PowerDown | Device powerdown routine
//  @comm      Called to suspend device.  You cannot call any routines aside from
//             those in your dll in this call.
//
//  This is a vestige of streaming driver interface. We don't use it for services.
//
extern "C" void NTP_PowerDown(void) {
    return;
}

//  @func BOOL   | NTP_IOControl | Device IO control routine
//  @parm DWORD  | dwOpenData | value returned from HLO_Open call
//  @parm DWORD  | dwCode | io control code to be performed
//  @parm PBYTE  | pBufIn | input data to the device
//  @parm DWORD  | dwLenIn | number of bytes being passed in
//  @parm PBYTE  | pBufOut | output data from the device
//  @parm DWORD  | dwLenOut |maximum number of bytes to receive from device
//  @parm PDWORD | pdwActualOut | actual number of bytes received from device
//  @rdesc         Returns TRUE for success, FALSE for failure
//  @remark	Routine exported by a device driver.  "PRF" is the string passed
//		in as lpszType in RegisterDevice
//
//  This is THE way to expose both manageability of the service and the feature API set.
//  Consumer of the API set will marshal input arguments into input buffer (pBufIn).
//  This function unmarshals it and executes the API, and then marshals output parameters
//  into output buffer (pBufOut).
//
extern "C" BOOL NTP_IOControl(DWORD dwData, DWORD dwCode, PBYTE pBufIn,
			  DWORD dwLenIn, PBYTE pBufOut, DWORD dwLenOut, PDWORD pdwActualOut) {
	DEBUGMSG(ZONE_TRACE, (L"[TIMESVC] NTP_IOControl 0x%08x\r\n", dwCode));

    int iErr  = ERROR_INVALID_PARAMETER;
    switch (dwCode) {
        case IOCTL_SERVICE_START: // start a service that is currently in the stopped stage.
			iErr = Exec (StartServer);
			if (iErr == ERROR_SUCCESS)
				StartOsNotifications ();
			break;

        case IOCTL_SERVICE_STOP: // start a service that is currently in the stopped stage.
            iErr = Exec (StopServer);
			if (iErr == ERROR_SUCCESS)
				StopOsNotifications ();
			break;

		case IOCTL_SERVICE_REFRESH:
			iErr = Exec (StopServer);
			if (iErr == ERROR_SUCCESS)
				iErr = Exec (StartServer);
			break;

		case IOCTL_SERVICE_STATUS:
			if (gpTS) {
				gpTS->Lock ();
				DWORD dwState = gpTS->IsStarted () ? SERVICE_STATE_ON : SERVICE_STATE_OFF;
				gpTS->Unlock ();

				if (pBufOut && (dwLenOut == sizeof(DWORD)))  {
					__try {
						*(DWORD *)pBufOut = dwState;

						if (pdwActualOut)
							*pdwActualOut = sizeof(DWORD);

						iErr = ERROR_SUCCESS;
					} __except (1) {
						iErr = ERROR_INVALID_PARAMETER;
					}
				} else
					iErr = ERROR_INVALID_PARAMETER;
			} else
				iErr = ERROR_SERVICE_DOES_NOT_EXIST;
			break;

		case IOCTL_SERVICE_CONTROL:	// sync/set/update
			if ((dwLenIn == sizeof(L"sync")) && (wcsicmp ((WCHAR *)pBufIn, L"sync") == 0))
				iErr = Exec (SyncTime);
			else if ((dwLenIn == sizeof(L"set")) && (wcsicmp ((WCHAR *)pBufIn, L"set") == 0))
				iErr = Exec (SetTime);
			else if ((dwLenIn == sizeof(L"update")) && (wcsicmp ((WCHAR *)pBufIn, L"update") == 0))
				iErr = Exec (ExternalTimeUpdate);
			break;

		case IOCTL_SERVICE_DEBUG:
#if defined (DEBUG) || defined (_DEBUG)
			if ((dwLenIn == sizeof(DWORD)) && pBufIn) {
				dpCurSettings.ulZoneMask = (*(DWORD *)pBufIn);
				iErr = ERROR_SUCCESS;
			} else
				iErr = ERROR_INVALID_PARAMETER;
#endif
			break;
    }

    SetLastError(iErr);
    return iErr == ERROR_SUCCESS;
}

#endif
#if ! defined (UNDER_CE)
int wmain (int argc, WCHAR **argv) {

	int iErr = InitializeFirstTime (NULL);

	if (iErr != ERROR_SUCCESS) {
		wprintf (L"Error %d in first-time initialization\n");
		return 1;
	}

	iErr = Exec (StartServer);

	if (iErr != ERROR_SUCCESS) {
		wprintf (L"Error %d starting service\n");
		return 1;
	}


	for ( ; ; ) {
		wprintf (L"Enter command: one of sync, set, update, start, stop, exit\n");

		WCHAR buffer[128];

		if (! fgetws (buffer, sizeof(buffer)/sizeof(buffer[0]), stdin))
			break;
		WCHAR *p = wcschr (buffer, L'\n');
		if (p)
			*p = '\0';
		if (wcsicmp (buffer, L"exit") == 0)
			break;

		if (wcsicmp (buffer, L"sync") == 0) {
			int iErr = Exec (SyncTime);
			if (iErr != ERROR_SUCCESS)
				wprintf (L"Error syncing time: %d\n", iErr);
		}

		if (wcsicmp (buffer, L"set") == 0) {
			int iErr = Exec(SetTime);
			if (iErr != ERROR_SUCCESS)
				wprintf (L"Error setting time update: %d\n", iErr);
		}

		if (wcsicmp (buffer, L"update") == 0) {
			int iErr = Exec(ExternalTimeUpdate);
			if (iErr != ERROR_SUCCESS)
				wprintf (L"Error updating time update: %d\n", iErr);
		}

		if (wcsicmp (buffer, L"netevent") == 0) {
			int iErr = Exec(NetworkChange);
			if (iErr != ERROR_SUCCESS)
				wprintf (L"Error tracking network: %d\n", iErr);
		}

		if (wcsicmp (buffer, L"start") == 0) {
			int iErr = Exec(StartServer);
			if (iErr != ERROR_SUCCESS)
				wprintf (L"Error starting service: %d\n", iErr);
		}

		if (wcsicmp (buffer, L"stop") == 0) {
			int iErr = Exec(StopServer);
			if (iErr != ERROR_SUCCESS)
				wprintf (L"Error stopping service: %d\n", iErr);
		}
	}

	iErr = Exec (StopServer);
	if (iErr != ERROR_SUCCESS)
		wprintf (L"Error %d stopping service\n");

	DestroyState ();

	return 0;
}
#endif




/*++
Routine Description:
    Determines real date that DST starts based on
    systemtime struct returned from GetTimeZoneInfo
    Copied from dst.cpp

Arguments:
    pointer to SYSTEMTIME structure
    
Return Value:
    TRUE if successful
    
--*/

BOOL DST_DetermineChangeDate(LPSYSTEMTIME pStTzChange, const SYSTEMTIME *pStNow)
{ 
    LONGLONG  llChangeDate = 0, llNow = 0;
    SYSTEMTIME stAbsolute; // Conversion from pStTzChange

    ASSERT((pStTzChange->wDay >= 1) && (pStTzChange->wDay <= 5));
    memcpy(&stAbsolute,pStTzChange,sizeof(stAbsolute));

    // build up the new structure
    if (stAbsolute.wYear == 0) {
        pStTzChange->wYear = stAbsolute.wYear = pStNow->wYear;
    } 

     // Day of week [Sun-Sat] the first day of week falls on
    WORD wFirstDayInMonth = GetFirstWeekDayOfMonth(&stAbsolute);

    // First day in month [1-7] that is on same day of week as timezone change
    WORD wFirstDayTZChange;

    // Determine the first date in this month that falls on the day of 
    // the week that the time zone change will happen on, eg 
    // first Monday in June 2006 would get wFirstDayTZChange=5.
    if (wFirstDayInMonth > pStTzChange->wDayOfWeek)
        wFirstDayTZChange = (1 + 7 -(wFirstDayInMonth - pStTzChange->wDayOfWeek));
    else
        wFirstDayTZChange = 1 + pStTzChange->wDayOfWeek - wFirstDayInMonth;

    ASSERT((wFirstDayTZChange >= 1) && (wFirstDayTZChange <= 7));

    stAbsolute.wDay = wFirstDayTZChange;
  
    for (DWORD i = 1; i < pStTzChange->wDay; i++) {
        FILETIME ftTemp;
        // Increment the current week by one, and see if it's a legit date.
        // SystemTimeToFileTime has all the logic to determine if the SystemTime
        // is correct - if it's not in means we've "overshot" the intended date
        // and need to pull back on.  Consider for instance if the TZ Cutover
        // was set to be 5th Thursday in July 2006.  Eventually in this loop
        // we would get to July 34th, which is bogus, so we'd need to pull
        // back to July 27th.
        stAbsolute.wDay += 7; 
         
        if (! SystemTimeToFileTime(&stAbsolute, &ftTemp)) {
            // We overshot, pull back a week
            stAbsolute.wDay -= 7;
            break;
        }     
    }
   
     // Need to determine if the next occurrance of this date is THIS year or NEXT year
    VERIFY(SystemTimeToFileTime(&stAbsolute, (FILETIME *)&llChangeDate));
    VERIFY(SystemTimeToFileTime(pStNow, (FILETIME *)&llNow));

    memcpy(pStTzChange,&stAbsolute,sizeof(stAbsolute));
    return TRUE;

}

// Determine first day of week [Sun-Sat] of the first day of the month.
//  Added from dst.cpp
WORD GetFirstWeekDayOfMonth(LPSYSTEMTIME pst) {
    SYSTEMTIME st;
    FILETIME   ft;
    memcpy(&st,pst,sizeof(st));
    st.wDayOfWeek = 1;
    st.wDay = 1;

    // Convert to FILETIME and then back to SYSTEMTIME to get this information.
    SystemTimeToFileTime(&st,&ft);
    FileTimeToSystemTime(&ft,&st);
    ASSERT((st.wDayOfWeek >= 0) && (st.wDayOfWeek <= 6));
    return st.wDayOfWeek;
}



/*++
Routine Description:
    Determines if we should auto-adjust for DST
    Added from dst.cpp

Arguments:
    none
    
Return Value:
    TRUE if we want to auto adjust
    FALSE if we don't want to auto adjust
    
--*/
BOOL  DST_Auto(void)
{
    DWORD ret = 0;
    HKEY hKey = NULL;
    if (ERROR_SUCCESS == RegOpenKeyEx(HKEY_LOCAL_MACHINE, RK_CLOCK, 0, 0, &hKey))
    {
        DWORD dwSize = sizeof(DWORD);
        RegQueryValueEx(hKey, RV_AUTODST, 0, NULL, (LPBYTE)&ret, &dwSize);
        RegCloseKey(hKey);
    }
#ifdef DEBUG
    if (ret)
    {
        DEBUGMSG(ZONE_CLIENT, ( _T("DST:  Auto change for DST is on")));
    } 
    else
    {
        DEBUGMSG(ZONE_CLIENT, ( _T("DST:  Auto change for DST is off")));
    }
#endif //DEBUG

    return (BOOL)ret;
}

/*++
Routine Description:
    determines if the current locale supports DST based on
    information in a TIME_ZONE_INFORMATION struct
    
Arguments:
    pointer to a TIME_ZONE_INFORMATION  struct
    
Return Value:
    TRUE if the locale supports DST, FALSE otherwise
    
--*/
BOOL LocaleSupportsDST(TIME_ZONE_INFORMATION *ptzi)
{
    ASSERT(ptzi);

    // If the month value is zero then this locale does not change for DST
    // it should never be the case that we have a DST date but not a SDT date
    ASSERT(((0 != ptzi->StandardDate.wMonth) && (0 != ptzi->DaylightDate.wMonth)) ||
           ((0 == ptzi->StandardDate.wMonth) && (0 == ptzi->DaylightDate.wMonth)));

    if ((0 != ptzi->StandardDate.wMonth) && (0 != ptzi->DaylightDate.wMonth))
        return TRUE;
    else
        return FALSE;
}


/*++
Routine Description:
    Changes system time to DST or STD.

Arguments:
    pST - UTC time on input, local time on output.
    
--*/
void SetDstOrStd(BOOL fIsAutoDstEnabled, SYSTEMTIME *pST) {
    LONGLONG  llStandard = 0, llDaylight = 0, llNow = 0;
    TIME_ZONE_INFORMATION tzi = {0};
    GetTimeZoneInformation(&tzi);

    if (! (fIsAutoDstEnabled && LocaleSupportsDST(&tzi)))
        return;

    VERIFY(SystemTimeToFileTime(pST, (FILETIME *)&llNow));    

    // Determine if we need to apply DST bias or not.  We can't trust the return
    // value from GetTimeZoneInformation() since the updated time hasn't been set yet
    // so we calculate this now.

    //fix up the date structs if necessary
    if (0 == tzi.StandardDate.wYear)
        DST_DetermineChangeDate(&tzi.StandardDate,pST);
    if (0 == tzi.DaylightDate.wYear)
        DST_DetermineChangeDate(&tzi.DaylightDate,pST);

    //convert so we can do the math
    VERIFY(SystemTimeToFileTime(&tzi.StandardDate, (FILETIME *)&llStandard));
    VERIFY(SystemTimeToFileTime(&tzi.DaylightDate, (FILETIME *)&llDaylight));

    // TIME_ZONE_INFORMATION values need to be converted to UTC.  We convert
    // llStandard using the DaylightBias because llStandard is the time in
    // DST local clock where the DST->STD conversion is performed.
    llStandard = llStandard + (((ULONGLONG)(tzi.Bias+tzi.DaylightBias))*FILETIME_TO_MINUTES);
    llDaylight = llDaylight + (((ULONGLONG)(tzi.Bias))*FILETIME_TO_MINUTES);

    // Determine if we should be in daylight or standard time.
    if ((llDaylight <= llStandard && llDaylight <= llNow && llNow <= llStandard) || 
        (llDaylight > llStandard && (llDaylight <= llNow || llNow <= llStandard)))
    {
        // We are in Daylight time.  Do the additional translation.  Note
        // that tzi.DaylightBias is always negative.
        SetDaylightTime(TRUE);
    }
    else {
        // In standard time, the offset between us & UTC returned 
        // by GetTimeZoneInformation is correct already.
        SetDaylightTime(FALSE);
    }//end of STD or DST check                                     

}

// This makes the thread running in explorer.exe that performs auto-setting DST 
// logic re-read its settings.  Setting timezone is an event it gets notified for.
void RefreshExplorerDST() {
    TIME_ZONE_INFORMATION tzi = {0};
    GetTimeZoneInformation(&tzi);
    SetTimeZoneInformation(&tzi);
}

#define DSTNOTIFICATION   _T("\\\\.\\Notifications\\NamedEvents\\ShellDSTEvent")
#define DSTTIMENOTIFICATION _T("\\\\.\\Notifications\\NamedEvents\\DSTTimeChange")
void DisableDSTNotifies()
{
    // disable all but TZ Change event.  I will use this event to restart the full DST processing.
    CeRunAppAtEvent(DSTTIMENOTIFICATION, NOTIFICATION_EVENT_NONE);
    CeRunAppAtTime(DSTNOTIFICATION, 0);
}
