/*****************************************************
 *
 *	tcp_time.c
 *	Copyright (C) Z-World, 2001
 *	
 * Demonstrate use of DeviceMate to talk to one or more "Time Protocol"
 * servers (see RFC868 at http://www.faqs.org/rfcs/rfc868.html).
 * You must run \samples\dmunit\tcp.c on the DeviceMate for this
 * sample to work (on the target processor).
 *
 * Specifically, the TCP/UDP tunneling facility of the DeviceMate is
 * used to connect to a sequence of Time servers.  This target processor
 * handles the Time protocol, which is extremely simple, leaving the
 * DeviceMate to handle the details of the ethernet/internet connection.
 *
 * You might like to use this as a basis for adding a reliable wall-clock
 * time to an appliance which only has a crystal oscillator and, up until
 * now, no means for setting the actual date and time.
 *
 * How it works: It depends on some RFC868 time servers being set up.  The
 * nominated server(s) are queried for their UTC time.  The result
 * from each server is adjusted for the network delay (assumed to be half
 * of the round-trip time).  If there were more than 2 servers, then the
 * servers whose times were "most distant" from the average of the others
 * are discarded, until a minumum of 2 or half the original number of
 * servers remain.  The final computed time is the average of the remaining
 * servers.  If all remaining servers are within about 60 seconds of each
 * other, then the discarding process is cut short.
 *
 * If no servers can be found, the current real-time clock is used
 * (SEC_TIMER).  This is a fall-back in case the network is unavailable.
 *
 * When the time is computed, this target processor then opens its own
 * Time and Daytime servers (RFC868 and RFC867).  You can then telnet to
 * port 13 on this processor to obtain the current time, or at least
 * its best guess at the time.
 *
 * Note that UDP is used to query the servers, but the target allows both
 * TCP and UDP queries to its server sockets.
 *
 * See also RFC2030 for the "real" way to do this -- this demo is "poor
 * man's NTP client" :-)
 *
 * Porting Note:
 *
 * This demo was written for the Rabbit.  However, since DeviceMate
 * functionality is specifically intended to be platform-independent, this
 * program should work with minor modification on other processors.  The
 * main areas to examine are:
 *   SEC_TIMER   - define this to be a macro which returns the number of
 *    seconds since some convenient epoch.  The rabbit uses 1980.  The
 *    RFC specifies 1900.  Redefine the EPOCH_DIFF symbol to be the
 *    correct number of seconds between the epochs.  Must be a uint32
 *    value.
 *   MS_TIMER    - should be a macro returning the number of milliseconds
 *    since an arbitrary epoch (e.g. bootup time).  Should be a uint16 or
 *    uint32 value.  The actual resolution does not have to be 1ms.  1/10
 *    second would be OK.
 *   format_timestamp()   - change this function to convert timestamps to
 *    a readable format.
 *   main()      - examine the DeviceMate setup etc.
 *   #use        - change to appropriate #include(s).
	
******************************************************/

/* Request use of TCP services */
#define USE_TC_TCPIP

/*
 * Uncomment this line if this program will be loaded over ethernet. This will
 * force the DevMate communication to use serial port A, re-using the programming
 * link that was used to download the program.
 */
//#define USE_TC_LOADER

#ifdef CC_VER
	/* Dynamic C */
	#memmap xmem
	#use "tc_conf.lib"
#else
	/* Non-Rabbit */
	#include "tc_conf.h"
	#include <stdio.h>	
	#include <time.h>	
	#include <math.h>	
#endif



#define TIME_PORT			37
#define DAYTIME_PORT		13

// Hostnames (or dotted quad IP addresses) of each time server to query.
// You can "weight" some servers as being more reliable by mentioning the
// same server in more than one entry.
// NOTE: the following addresses were obtained from hosts that were
// denoted as "open access" (http://www.eecis.udel.edu/~mills/ntp/servers.htm)
// to NTP.  We don't use NTP, but we don't think they'd mind if we used the
// old time service.  Please don't incorporate these addresses in any product
// without prior approval of the host adinistrator!
// The timestamps from these servers will be traceable to national standards.
// Since the DeviceMate will initially need to use DNS to look up the hosts'
// IP addresses, this will introduce an unknown amount of latency when getting
// the timestamps.  For best accuracy, run this demo a second time, which will
// ensure that the addresses are cached locally thus reducing the latency.
#define NSERVERS		4
const char * server_hosts[NSERVERS] =
{
   "time-b.nist.gov"
  ,"nist1.aol-ca.truetime.com"
  ,"ntp.adelaide.edu.au"
  ,"timelord.uregina.ca"
};

// Returned timestamps from servers.  These are stored as the number of seconds
// since Jan 1, 1900 UT.  They are later converted to "Rabbit epoch" (1980).
// There were lots of furry rabbits at that time but, strangely, no silicon ones.
// Sadly, RFC868 does not allow determination of timezone, so everything is in
// terms of UTC a.k.a. "GMT".
uint32 timestamps[NSERVERS];

// Flag to indicate whether valid result obtained from corresponding server.
int use_this[NSERVERS];

// Number of valid results remaining.
int nservers;

// Base "reference" epoch (local ms) for when we started our timings.
uint32 base_stamp;

// Base epoch from SEC_TIMER corresponding to the above
uint32 base_tick;

// Amount to add to SEC_TIMER to make the result equal to the average time
// derived from the servers.
uint32 skew;

#ifdef __DC__
	// Seconds between RFC868 epoch (1900) and Rabbit epoch (1980)
	#define EPOCH_DIFF	2524521600UL
#else
	// Assume 1970 epoch
	#define EPOCH_DIFF	2208988800UL
#endif

char * format_timestamp(uint32 stamp)
{
	static char b[40];
	static const char * weekdays[7] =
		{ "Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat" };
	static const char * months[12] =
		{ "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec" };
#ifdef __DC__
	static struct tm daytime;

	mktm(&daytime, stamp);
	sprintf(b, "%s %s %d %02d:%02d:%02d %d UTC",
							weekdays[daytime.tm_wday],
							months[daytime.tm_mon - 1],
							daytime.tm_mday,
							daytime.tm_hour,
							daytime.tm_min,
							daytime.tm_sec,
							daytime.tm_year + 1900
							);
#else
	struct tm * tmp;
	time_t tt;
	
	tt = (time_t)stamp;
	tmp = gmtime(&tt);
	sprintf(b, "%s %s %d %02d:%02d:%02d %d UTC",
							weekdays[tmp->tm_wday],
							months[tmp->tm_mon - 1],
							tmp->tm_mday,
							tmp->tm_hour,
							tmp->tm_min,
							tmp->tm_sec,
							tmp->tm_year + 1900
							);
#endif
	return b;
}


void query_the_servers(void)
{
	auto char rxbuf[4];
	auto char rev_rxbuf[4];
	auto int rc;
	auto int socket;
	auto int i;
	auto uint32 rhost;
	auto uint16 rport;
	auto int nrx;
	auto uint32 rtt_stamp;
	auto uint32 stamp;
	auto int rt_count;
	
	nservers = 0;
	socket = 0;

	// Remember starting epoch for our queries
	base_stamp = MS_TIMER;
	base_tick = SEC_TIMER;

	for (i = 0; i < NSERVERS; i++) {

		use_this[i] = 0;

		// Close the UDP socket if it was in use by previous iteration of this loop.
		if (socket) {
			devmate_udp_close(socket);
			socket = 0;
		}

		// Open UDP channel.
		while (!(socket = devmate_udp_open(1, 0, (char *)server_hosts[i], TIME_PORT)))
			devmate_tick();
		if (socket < 0) {
			printf("Could not open UDP socket to %s.\n", server_hosts[i]);
			exit(21);
		}

		// Wait for establishment (to the DeviceMate, not the server since UDP
		// is connectionless).
		while (!devmate_tcp_isestablished(socket));

		// Now may be established, but could also be error.
		if (devmate_tcp_error(socket)) {
			printf("Could not resolve %s.\n", server_hosts[i]);
			continue;	// Try next server
		}

		rt_count = 0;
		
	retry:
		// All OK.  Send empty datagram to server after recording current
		// (local) timestamp.  This loops until the return is non-zero.
		while (!(rc = devmate_udp_send(socket, NULL, 0, 0)));
		
		if (rc < 0) {
			printf("Failed to send empty datagram to %s.\n", server_hosts[i]);
			continue;
		}

		rtt_stamp = MS_TIMER;	// Start time of issuing request

		for (;;) {
			rc = devmate_udp_recvfrom(socket, &rhost, &rport);
			if (rc < 0) {
				printf("Failed to get response from %s: error code %d.\n",
					server_hosts[i],
					devmate_tcp_error(socket));
				break;
			}
			if (rport)
				// Exit wait loop if got response.
				break;

			// 4 second timeout.  Retry up to 2 times, else ignore this server.  This raises
			// the possibility that we may get a response from a previous packet, thus
			// warping our perception of time.  We ignore this wrinkle in our demo, since
			// the protocol does not provide for correction of this other than waiting about
			// 4 minutes for stray packets to die in the network.
			if ((long)(MS_TIMER - rtt_stamp) > 4000) {
				rt_count++;
				if (rt_count < 2)
					goto retry;
				else {
					printf("Server %s failed to respond.\n", server_hosts[i]);
					rc = -1;
					break;
				}
			}
		}

		if (rc < 0)
			continue;

		// Correct for round-trip time in milliseconds.
		rtt_stamp += MS_TIMER - rtt_stamp >> 1;
		
		// Read datagram in 'blocking' mode
		printf("Got %d bytes from host %s [%lX] port %u\n", rc, server_hosts[i], rhost, rport);
		nrx = 0;
		while (nrx < rc)
			nrx = devmate_udp_recvdata(socket, rxbuf, 4, nrx);

		// Interpret the response
		if (nrx >= 4) {
#ifndef TC_BIG_ENDIAN
			// Good, reverse byte order.
			rev_rxbuf[0] = rxbuf[3];
			rev_rxbuf[1] = rxbuf[2];
			rev_rxbuf[2] = rxbuf[1];
			rev_rxbuf[3] = rxbuf[0];
			memcpy((char *)&stamp, rev_rxbuf, 4);
#else
			memcpy((char *)&stamp, rxbuf, 4);
#endif

			// Add 1/2 round-trip time and correction back to common base epoch
			stamp -= (rtt_stamp - base_stamp)/ 1000;

			timestamps[i] = stamp;
			use_this[i] = 1;			// Mark as OK so far
			printf("Server %s says time is %s\n", server_hosts[i], format_timestamp(stamp - EPOCH_DIFF));
			nservers++;
		}
	}

	if (socket)
		devmate_udp_close(socket);

	devmate_tick();	// Allow close to be processed

}


void discard_outliers(void)
{
	auto int i, j, k;
	auto int minservers;
	auto float diff;
	auto float wdiff;
	auto float mdiff;

	if (nservers >= 3)
		minservers = nservers + 1 >> 1;	// Half (rounded up) of the successful servers
	else
		minservers = nservers;

	while (nservers > minservers) {
		// Throw away the worst outliers first, until minservers remain.  The
		// "worst outlier" is the one whose sum-of-squares distance from the others
		// is greatest.
		mdiff = 0.0;
		for (i = 0; i < NSERVERS; i++)
			if (!use_this[i])
				continue;
			else {
				wdiff = 0.0;
				for (j = 0; j < NSERVERS; j++)
					if (!use_this[j] || i == j)
						continue;
					else {
						diff = (long)(timestamps[j] - timestamps[i]);
						wdiff += diff * diff;
					}
				if (wdiff > mdiff) {
					mdiff = wdiff;
					k = i;
				}
			}
		if (sqrt(mdiff) / nservers < 60)
			// Don't discard any more if within about 60 seconds of each other
			break;
		use_this[k] = 0;
		printf("Discarding server %d\n", k);
		nservers--;
	}
}

void compute_offset(void)
{
	auto int i;
	auto long stamp;
	auto long avg;
	
	
	// Average the timestamps of the remaining servers.  This can't be
	// done the obvious way, to avoid arithmetic overflow.

	for (i = 0; i < NSERVERS; i++)
		if (use_this[i]) {
			avg = timestamps[i];
			break;
		}

	stamp = 0;
	for (i++; i < NSERVERS; i++)
		if (use_this[i])
			stamp += timestamps[i] - avg;

	avg += stamp / nservers;

	// Convert to "Rabbit epoch" by subtracting the number of seconds between
	// 1900 and 1980.
	avg -= EPOCH_DIFF;

	// This is the amount to add to SEC_TIMER to get the "correct" time.
	skew = avg - base_tick;
}


void become_server(void)
{
	// Become a four-port server, for both TCP and UDP versions of Time (RFC868)
	// and Daytime (RFC867).  The latter is an ascii (human readable) version of
	// Time.

	// Socket numbers to use for the various services
#define SOCK_DAYTIME_TCP	1
#define SOCK_TIME_TCP		2
#define SOCK_DAYTIME_UDP	3
#define SOCK_TIME_UDP		4
	auto char buf[4][64];
	auto int rc;
	auto int sock, sm1;
	auto uint16 rport[4];
	auto uint32 rhost[4];
	auto int st, state[4];
#define S_LISTEN	1
#define S_DATE		2
#define S_CLOSE	3
#define S_CLOSING	4
#define S_CLOSED	5
	auto int ntx, num_tx[4];
	auto int len;
	auto char * b, * b2;
	auto char * sname;
	auto long stamp;
	auto int udp;
	static const char * socknames[4] =
		{ "DAYTIME TCP", "TIME TCP", "DAYTIME UDP", "TIME UDP" };
	

	/*
	 * Open sockets in passive mode
	 */
	for (sock = 0; sock < 4; sock++) {
		state[sock] = S_CLOSED;
	}

	for (sock = 1;;) {
		sm1 = sock - 1;
		st = state[sm1];
		b = &buf[sm1][0];
		ntx = num_tx[sm1];
		sname = (char *)socknames[sm1];
		udp = sock >= SOCK_DAYTIME_UDP;

		switch (st) {
			case S_LISTEN:
				if (udp) {
					devmate_udp_recvfrom(sock, rhost+sm1, rport+sm1);
					rc = rport[sm1];
				}
				else
					rc = devmate_tcp_isestablished(sock);
				if (rc) {
					printf("Socket %s query received\n", sname);
					ntx = num_tx[sm1] = 0;
					st = S_DATE;	// fall thru
					if (sock == SOCK_DAYTIME_TCP ||
						 sock == SOCK_DAYTIME_UDP) {
						sprintf(b, "%s - RabbitTime\r\n", format_timestamp(SEC_TIMER + skew));
					}
					else {
						// Convert to 1900 epoch
						stamp = SEC_TIMER + skew + EPOCH_DIFF;
#ifndef TC_BIG_ENDIAN
						b2 = (char *)&stamp;
						// Turn into network byte order
						b[0] = b2[3];
						b[1] = b2[2];
						b[2] = b2[1];
						b[3] = b2[0];
#else
						memcpy(b, &stamp, 4);
#endif
					}
				}
				else
					break;
			case S_DATE:
				if (sock == SOCK_DAYTIME_TCP ||
					 sock == SOCK_DAYTIME_UDP)
					len = strlen(b);
				else
					len = 4;
				if (ntx >= len) {
					printf("Socket %s done\n", sname);
					st = S_CLOSE;
					break;
				}
				if (udp)
					rc = devmate_udp_sendto(sock, b, len, ntx, rhost[sm1], rport[sm1]);
				else
					rc = devmate_tcp_fastwrite(sock, b+ntx, len-ntx);
				if (rc < 0) {
					printf("Write error sock %s\n", sname);
					st = S_CLOSE;
					break;
				}
				
				if (udp)
					ntx = rc;
				else
					ntx += rc;
				break;
			case S_CLOSE:
				if (udp) {
					// Bypass the close stuff for UDP
					st = S_LISTEN;
					break;
				}
				devmate_tcp_close(sock);
				printf("socket %s closing...\n", sname);
				st = S_CLOSING;
				// fall thru
			case S_CLOSING:
				if (!devmate_tcp_isclosed(sock))
					break;
				
				printf("...socket %s closed.\n", sname);
				st = S_CLOSED;
				// fall thru;
			case S_CLOSED:
				if (udp)
					rc = devmate_udp_open(sock,
							sock == SOCK_DAYTIME_UDP ? DAYTIME_PORT : TIME_PORT,
							NULL, 0);
				else
					rc = devmate_tcp_listen(sock,
							sock == SOCK_DAYTIME_TCP ? DAYTIME_PORT : TIME_PORT,
							NULL, 0);
				if (rc > 0) {
					printf("Socket %s ready for query\n", sname);
					st = S_LISTEN;
				}
				else if (rc < 0) {
					printf("Socket %s listen failed\n", sname);
					// Failing to listen indicates serious error.
					exit(56);
				}
				break;
		}
		
		devmate_tick();

		state[sm1] = st;
		num_tx[sm1] = ntx;
		sock++;
		if (sock == 5)
			sock = 1;
	}
	
}

#ifdef __DC__
root
#endif
int main()
{
	int rc;

	devmate_init();
	
	rc = devmate_sock_init(NULL, NULL, NULL, NULL, NULL, NULL);
	if (rc <= 0) {
		printf("DeviceMate not available.\n");
		exit(20);
	}

	// Query the Time protocol port on each server.
	
	query_the_servers();

	if (!nservers) {
		printf("No servers could be found - using local clock\n");
		skew = 0;
	}
	else {
		// Now that we have responses from hopefully all of the servers, determine the
		// set that we are going to average.

		discard_outliers();

		// Compute the offset that we need to add to our SEC_TIMER to get the
		// correct averaged time.

		compute_offset();
	}

	// Now turn into a Time/Daytime protocol server.
	become_server();

	return 0;
}
