/* 
 * sht.c - Testprogram for shared memory refclock
 * read/write shared memory segment; see usage
 */
#include "config.h"

#ifndef SYS_WINNT
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <stdio.h>
#include <time.h>
#include <unistd.h>
#include <stdlib.h>
#else
#include <windows.h>
#include <time.h>
#include <stdlib.h>
#include <stdio.h>
#include <iostream.h>
#define sleep(x) Sleep(x*1000)
#endif
#include <assert.h>

struct shmTime {
	int    mode; /* 0 - if valid set
		      *       use values, 
		      *       clear valid
		      * 1 - if valid set 
		      *       if count before and after read of values is equal,
		      *         use values 
		      *       clear valid
		      */
	volatile int	count;
	time_t		clockTimeStampSec;
	int		clockTimeStampUSec;
	time_t		receiveTimeStampSec;
	int		receiveTimeStampUSec;
	int		leap;
	int		precision;
	int		nsamples;
	volatile int	valid;
	unsigned	clockTimeStampNSec;	/* Unsigned ns timestamps */
	unsigned	receiveTimeStampNSec;	/* Unsigned ns timestamps */
};

static struct shmTime *
getShmTime (
	int unit
	)
{
#ifndef SYS_WINNT
	int shmid=shmget (0x4e545030+unit, sizeof (struct shmTime), IPC_CREAT|0777);
	if (shmid==-1) {
		perror ("shmget");
		exit (1);
	}
	else {
		struct shmTime *p=(struct shmTime *)shmat (shmid, 0, 0);
		if ((int)(long)p==-1) {
			perror ("shmat");
			p=0;
		}
		assert (p!=0);
		return p;
	}
#else
	char buf[10];
	LPSECURITY_ATTRIBUTES psec=0;
	snprintf (buf, sizeof(buf), "NTP%d", unit);
	SECURITY_DESCRIPTOR sd;
	SECURITY_ATTRIBUTES sa;
	HANDLE shmid;

	assert (InitializeSecurityDescriptor(&sd, SECURITY_DESCRIPTOR_REVISION));
	assert (SetSecurityDescriptorDacl(&sd,1,0,0));
	sa.nLength=sizeof (SECURITY_ATTRIBUTES);
	sa.lpSecurityDescriptor=&sd;
	sa.bInheritHandle=0;
	shmid=CreateFileMapping ((HANDLE)0xffffffff, 0, PAGE_READWRITE,
				 psec, sizeof (struct shmTime),buf);
	if (!shmid) {
		shmid=CreateFileMapping ((HANDLE)0xffffffff, 0, PAGE_READWRITE,
					 0, sizeof (struct shmTime),buf);
		cout <<"CreateFileMapping with psec!=0 failed"<<endl;
	}

	if (!shmid) {
		char mbuf[1000];
		FormatMessage (FORMAT_MESSAGE_FROM_SYSTEM,
			       0, GetLastError (), 0, mbuf, sizeof (mbuf), 0);
		int x=GetLastError ();
		cout <<"CreateFileMapping "<<buf<<":"<<mbuf<<endl;
		exit (1);
	}
	else {
		struct shmTime *p=(struct shmTime *) MapViewOfFile (shmid, 
								    FILE_MAP_WRITE, 0, 0, sizeof (struct shmTime));
		if (p==0) {
			char mbuf[1000];
			FormatMessage (FORMAT_MESSAGE_FROM_SYSTEM,
				       0, GetLastError (), 0, mbuf, sizeof (mbuf), 0);
			cout <<"MapViewOfFile "<<buf<<":"<<mbuf<<endl;
			exit (1);
		}
		return p;
	}
	return 0;
#endif
}


int
main (
	int argc,
	char *argv[]
	)
{
	volatile struct shmTime *p;
	int unit;
	char *argp;

	if (argc<=1) {
	  usage:
		printf ("usage: %s [uu:]{r[c][l]|w|snnn}\n",argv[0]);
		printf ("       uu use clock unit uu (default: 2)\n");
		printf ("       r read shared memory\n");
		printf ("       c clear valid-flag\n");
		printf ("       l loop (so, rcl will read and clear in a loop\n");
		printf ("       w write shared memory with current time\n");
		printf ("       snnnn set nsamples to nnn\n");
		printf ("       lnnnn set leap to nnn\n");
		printf ("       pnnnn set precision to -nnn\n");
		exit (0);
	}

	srand(time(NULL));
		
	unit = strtoul(argv[1], &argp, 10);
	if (argp == argv[1])
		unit = 2;
	else if (*argp == ':')
		argp++;
	else
		goto usage;

	p=getShmTime(unit);
	switch (*argp) {
	case 's':
		p->nsamples=atoi(argp+1);
		break;

	case 'l':
		p->leap=atoi(argp+1);
		break;

	case 'p':
		p->precision=-atoi(argp+1);
		break;

	case 'r': {
		int clear=0;
		int loop=0;
		printf ("reader\n");		
		while (*++argp) {
			switch (*argp) {
			case 'l': loop=1; break;
			case 'c': clear=1; break;
			default : goto usage;
			}
		}
again:
		printf ("mode=%d, count=%d, clock=%ld.%09u, rec=%ld.%09u,\n",
			p->mode,p->count,
			(long)p->clockTimeStampSec,p->clockTimeStampNSec,
			(long)p->receiveTimeStampSec,p->receiveTimeStampNSec);
		printf ("  leap=%d, precision=%d, nsamples=%d, valid=%d\n",
			p->leap, p->precision, p->nsamples, p->valid);
		if (!p->valid)
			printf ("***\n");
		if (clear) {
			p->valid=0;
			printf ("cleared\n");
		}
		if (loop) {
			sleep (1);
			goto again;
		}
		break;
	}

	case 'w': {
		/* To show some life action, we read the system
		 * clock and use a bit of fuzz from 'random()' to get a
		 * bit of wobbling into the values (so we can observe a
		 * certain jitter!)
		 */
		time_t clk_sec, rcv_sec;
		uint   clk_frc, rcv_frc;

#if defined(HAVE_CLOCK_GETTIME) && defined(CLOCK_REALTIME)
		
		/* Here we have a high-resolution system clock, and
		 * we're not afraid to use it!
		 */
		struct timespec tmptime;
		if (0 == clock_gettime(CLOCK_REALTIME, &tmptime)) {
			rcv_sec = tmptime.tv_sec;
			rcv_frc = (uint)tmptime.tv_nsec;
		}
		else
#endif
		{
			time(&rcv_sec);
			rcv_frc = (uint)random() % 1000000000u;
		}
		/* add a wobble of ~3.5msec to the clock time */
		clk_sec = rcv_sec;
		clk_frc = rcv_frc + (uint)(random()%7094713 - 3547356);
		/* normalise result -- the SHM driver is picky! */
		while ((int)clk_frc < 0) {
			clk_frc += 1000000000;
			clk_sec -= 1;
		}
		while ((int)clk_frc >= 1000000000) {
			clk_frc -= 1000000000;
			clk_sec += 1;
		}
		
		/* Most 'real' time sources would create a clock
		 * (reference) time stamp where the fraction is zero,
		 * but that's not an actual requirement. So we show how
		 * to deal with the time stamps in general; changing the
		 * behaviour for cases where the fraction of the
		 * clock time is zero should be trivial.
		 */ 
		printf ("writer\n");
		p->mode=0;
		if (!p->valid) {
			p->clockTimeStampSec    = clk_sec;
			p->clockTimeStampUSec   = clk_frc / 1000; /* truncate! */
			p->clockTimeStampNSec   = clk_frc;
			p->receiveTimeStampSec  = rcv_sec;
			p->receiveTimeStampUSec = rcv_frc / 1000; /* truncate! */
			p->receiveTimeStampNSec = rcv_frc;
			printf ("%ld.%09u %ld.%09u\n",
				(long)p->clockTimeStampSec  , p->clockTimeStampNSec  ,
				(long)p->receiveTimeStampSec, p->receiveTimeStampNSec);
			p->valid=1;
		}
		else {
			printf ("p->valid still set\n"); /* not an error! */
		}
		break;
	}
	default:
		break;
	}
	return 0;
}
