/*
 * This software was developed by the Software and Component Technologies
 * group of Trimble Navigation, Ltd.
 *
 * Copyright (c) 1997, 1998, 1999, 2000  Trimble Navigation Ltd.
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 * 3. All advertising materials mentioning features or use of this software
 *    must display the following acknowledgement:
 *    This product includes software developed by Trimble Navigation, Ltd.
 * 4. The name of Trimble Navigation Ltd. may not be used to endorse or
 *    promote products derived from this software without specific prior
 *    written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY TRIMBLE NAVIGATION LTD. ``AS IS'' AND
 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED.  IN NO EVENT SHALL TRIMBLE NAVIGATION LTD. BE LIABLE
 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
 * SUCH DAMAGE.
 */

/*
 * refclock_palisade - clock driver for the Trimble Palisade GPS
 * timing receiver
 *
 * For detailed information on this program, please refer to the html 
 * Refclock 29 page accompanying the NTP distribution.
 *
 * for questions / bugs / comments, contact:
 * sven_dietrich@trimble.com
 *
 * Sven-Thorsten Dietrich
 * 645 North Mary Avenue
 * Post Office Box 3642
 * Sunnyvale, CA 94088-3642
 *
 * Version 2.45; July 14, 1999
 *
 *
 *
 * 31/03/06: Added support for Thunderbolt GPS Disciplined Clock.
 *	     Contact: Fernando Pablo Hauscarriaga
 * 	     E-mail: fernandoph@iar.unlp.edu.ar
 * 	     Home page: www.iar.unlp.edu.ar/~fernandoph
 *		  Instituto Argentino de Radioastronomia
 *			    www.iar.unlp.edu.ar
 *
 * 14/01/07: Conditinal compilation for Thunderbolt support no longer needed
 *	     now we use mode 2 for decode thunderbolt packets.
 *	     Fernando P. Hauscarriaga
 *
 * 30/08/09: Added support for Trimble Acutime Gold Receiver.
 *	     Fernando P. Hauscarriaga (fernandoph@iar.unlp.edu.ar)
 *
 * 21/04/18: Added support for Resolution devices.
 *
 * 03/09/19: Added support for ACE III & Copernicus II.
 */

#ifdef HAVE_CONFIG_H
# include "config.h"
#endif

#if defined(REFCLOCK) && defined(CLOCK_PALISADE)

#ifdef SYS_WINNT
extern int async_write(int, const void *, unsigned int);
#undef write
#define write(fd, data, octets)	async_write(fd, data, octets)
#endif

#include "refclock_palisade.h"

#ifdef DEBUG
const char * Tracking_Status[15][15] = { 
	{ "Doing Fixes\0" }, { "Good 1SV\0" }, { "Approx. 1SV\0" },
	{"Need Time\0" }, { "Need INIT\0" }, { "PDOP too High\0" },
	{ "Bad 1SV\0" }, { "0SV Usable\0" }, { "1SV Usable\0" },
	{ "2SV Usable\0" }, { "3SV Usable\0" }, { "No Integrity\0" },
	{ "Diff Corr\0" }, { "Overdet Clock\0" }, { "Invalid\0" } };
#endif

/*
 * Transfer vector
 */
struct refclock refclock_palisade = {
	palisade_start,		/* start up driver */
	palisade_shutdown,	/* shut down driver */
	palisade_poll,		/* transmit poll message */
	noentry,		/* not used  */
	noentry,		/* initialize driver (not used) */
	noentry,		/* not used */
	NOFLAGS			/* not used */
};

static int decode_date(struct refclockproc *pp, const char *cp);

/* Extract the clock type from the mode setting */
#define CLK_TYPE(x) ((int)(((x)->ttl) & 0x7F))

/* Supported clock types */
#define CLK_TRIMBLE	0	/* Trimble Palisade */
#define CLK_PRAECIS	1	/* Endrun Technologies Praecis */
#define CLK_THUNDERBOLT	2	/* Trimble Thunderbolt GPS Receiver */
#define CLK_ACUTIME     3	/* Trimble Acutime Gold */
#define CLK_ACUTIMEB    4	/* Trimble Actutime Gold Port B */
#define CLK_RESOLUTION  5	/* Trimble Resolution Receivers */
#define CLK_ACE		6	/* Trimble ACE III */
#define CLK_COPERNICUS	7	/* Trimble Copernicus II */

int praecis_msg;
static void praecis_parse(struct recvbuf *rbufp, struct peer *peer);

/* These routines are for sending packets to the Thunderbolt receiver
 * They are taken from Markus Prosch
 */

/*
 * sendcmd - Build data packet for sending
 */
static void 
sendcmd (
	struct packettx *buffer,
	int c
	)
{
	*buffer->data = DLE;
	*(buffer->data + 1) = (unsigned char)c;
	buffer->size = 2;
}

/*
 * sendsupercmd - Build super data packet for sending
 */
static void 
sendsupercmd (
	struct packettx *buffer,
	int c1,
	int c2
	)
{
	*buffer->data = DLE;
	*(buffer->data + 1) = (unsigned char)c1;
	*(buffer->data + 2) = (unsigned char)c2;
	buffer->size = 3;
}

/*
 * sendbyte -
 */
static void 
sendbyte (
	struct packettx *buffer,
	int b
	)
{
	if (b == DLE)
		*(buffer->data+buffer->size++) = DLE;
	*(buffer->data+buffer->size++) = (unsigned char)b;
}

/*
 * sendint -
 */
static void 
sendint (
	struct packettx *buffer,
	int a
	)
{
	sendbyte(buffer, (unsigned char)((a>>8) & 0xff));
	sendbyte(buffer, (unsigned char)(a & 0xff));
}

/*
 * sendetx - Send packet or super packet to the device
 */
static int 
sendetx (
	struct packettx *buffer,
	int fd
	)
{
	int result;
	
	*(buffer->data+buffer->size++) = DLE;
	*(buffer->data+buffer->size++) = ETX;
	result = write(fd, buffer->data, (unsigned long)buffer->size);
	
	if (result != -1)
		return (result);
	else
		return (-1);
}

/*
 * init_thunderbolt - Prepares Thunderbolt receiver to be used with
 *		      NTP (also taken from Markus Prosch).
 */
static void
init_thunderbolt (
	int fd
	)
{
	struct packettx tx;
	
	tx.size = 0;
	tx.data = (u_char *) emalloc(100);

	/* set UTC time */
	sendsupercmd (&tx, 0x8E, 0xA2);
	sendbyte     (&tx, 0x3);
	sendetx      (&tx, fd);
	
	/* activate packets 0x8F-AB and 0x8F-AC */
	sendsupercmd (&tx, 0x8E, 0xA5);
	sendint      (&tx, 0x5);
	sendetx      (&tx, fd);

	free(tx.data);
}

/*
 * init_acutime - Prepares Acutime Receiver to be used with NTP
 */
static void
init_acutime (
	int fd
	)
{
	/* Disable all outputs, Enable Event-Polling on PortA so
	   we can ask for time packets */
	struct packettx tx;

	tx.size = 0;
	tx.data = (u_char *) emalloc(100);

	sendsupercmd(&tx, 0x8E, 0xA5);
	sendbyte(&tx, 0x02);
	sendbyte(&tx, 0x00);
	sendbyte(&tx, 0x00);
	sendbyte(&tx, 0x00);
	sendetx(&tx, fd);

	free(tx.data);
}

/*
 * init_resolution - Prepares Resolution receiver to be used with NTP
 */
static void
init_resolution (
	int fd
	)
{
	struct packettx tx;
	
	tx.size = 0;
	tx.data = (u_char *) emalloc(100);

	/* set UTC time */
	sendsupercmd (&tx, 0x8E, 0xA2);
	sendbyte     (&tx, 0x3);
	sendetx      (&tx, fd);

	/* squelch PPS output unless locked to at least one satellite */
	sendsupercmd (&tx, 0x8E, 0x4E);
	sendbyte     (&tx, 0x3);
	sendetx      (&tx, fd);
	
	/* activate packets 0x8F-AB and 0x8F-AC */
	sendsupercmd (&tx, 0x8E, 0xA5);
	sendint      (&tx, 0x5);
	sendetx      (&tx, fd);

	free(tx.data);
}

/*
 * palisade_start - open the devices and initialize data for processing
 */
static int
palisade_start (
	int unit,
	struct peer *peer
	)
{
	struct palisade_unit *up;
	struct refclockproc *pp;
	int fd;
	char gpsdev[20];
	struct termios tio;
	u_int speed;

	snprintf(gpsdev, sizeof(gpsdev), DEVICE, unit);

	/*
	 * Open serial port. 
	 */
	speed = (CLK_TYPE(peer) == CLK_COPERNICUS) ? SPEED232COP : SPEED232;
	fd = refclock_open(&peer->srcadr, gpsdev, speed, LDISC_RAW);
	if (fd <= 0) {
#ifdef DEBUG
		printf("Palisade(%d) start: open %s failed\n", unit, gpsdev);
#endif
		return 0;
	}

	msyslog(LOG_NOTICE, "Palisade(%d) fd: %d dev: %s", unit, fd,
		gpsdev);

	if (tcgetattr(fd, &tio) < 0) {
		msyslog(LOG_ERR, 
			"Palisade(%d) tcgetattr(fd, &tio): %m",unit);
#ifdef DEBUG
		printf("Palisade(%d) tcgetattr(fd, &tio)\n",unit);
#endif
		close(fd);
		return (0);
	}

	tio.c_cflag |= (PARENB|PARODD);
	tio.c_iflag &= ~ICRNL;

	/*
	 * Allocate and initialize unit structure
	 */
	up = emalloc_zero(sizeof(*up));

	up->type = CLK_TYPE(peer);
	switch (up->type) {
	    case CLK_TRIMBLE:
		/* Normal mode, do nothing */
		break;
	    case CLK_PRAECIS:
		msyslog(LOG_NOTICE, "Palisade(%d) Praecis mode enabled"
			,unit);
		break;
	    case CLK_THUNDERBOLT:
		msyslog(LOG_NOTICE, "Palisade(%d) Thunderbolt mode enabled"
			,unit);
		tio.c_cflag = (CS8|CLOCAL|CREAD);
		break;
	    case CLK_ACUTIME:
		msyslog(LOG_NOTICE, "Palisade(%d) Acutime Gold mode enabled"
			,unit);
		break;
	    case CLK_RESOLUTION:
		msyslog(LOG_NOTICE, "Palisade(%d) Resolution mode enabled"
			,unit);
		tio.c_cflag = (CS8|CLOCAL|CREAD|PARENB|PARODD);
		break;
	    case CLK_ACE:
		msyslog(LOG_NOTICE, "Palisade(%d) ACE III mode enabled"
			,unit);
		tio.c_cflag = (CS8|CLOCAL|CREAD|PARENB|PARODD);
		break;
	    case CLK_COPERNICUS:
		msyslog(LOG_NOTICE, "Palisade(%d) Copernicus II mode enabled"
			,unit);
		/* Must use ORing/ANDing to set/clear c_cflag bits otherwise
		   CBAUD gets set back to 0. This ought to be an issue for
		   the other modes above but it seems that the baud rate
		   defaults to 9600 if CBAUD gets set to 0.                 */
		tio.c_cflag &= ~(PARENB|PARODD);
		break;
	    default:
		msyslog(LOG_NOTICE, "Palisade(%d) mode unknown",unit);
		break;
	}
	if (tcsetattr(fd, TCSANOW, &tio) == -1) {
		msyslog(LOG_ERR, "Palisade(%d) tcsetattr(fd, &tio): %m",unit);
#ifdef DEBUG
		printf("Palisade(%d) tcsetattr(fd, &tio)\n",unit);
#endif
		close(fd);
		free(up);
		return 0;
	}

	pp = peer->procptr;
	pp->io.clock_recv = palisade_io;
	pp->io.srcclock = peer;
	pp->io.datalen = 0;
	pp->io.fd = fd;
	if (!io_addclock(&pp->io)) {
#ifdef DEBUG
		printf("Palisade(%d) io_addclock\n",unit);
#endif
		close(fd);
		pp->io.fd = -1;
		free(up);
		return (0);
	}

	/*
	 * Initialize miscellaneous variables
	 */
	pp->unitptr = up;
	pp->clockdesc = DESCRIPTION;

	peer->precision = PRECISION;
	peer->sstclktype = CTL_SST_TS_UHF;
	peer->minpoll = TRMB_MINPOLL;
	peer->maxpoll = TRMB_MAXPOLL;
	memcpy((char *)&pp->refid, REFID, 4);
	
	up->leap_status = 0;
	up->unit = (short) unit;
	up->rpt_status = TSIP_PARSED_EMPTY;
	up->rpt_cnt = 0;

	if (up->type == CLK_THUNDERBOLT)
		init_thunderbolt(fd);
	if (up->type == CLK_ACUTIME)
		init_acutime(fd);
	if (up->type == CLK_RESOLUTION)
		init_resolution(fd);

	return 1;
}


/*
 * palisade_shutdown - shut down the clock
 */
static void
palisade_shutdown (
	int unit,
	struct peer *peer
	)
{
	struct palisade_unit *up;
	struct refclockproc *pp;
	pp = peer->procptr;
	up = pp->unitptr;
	if (-1 != pp->io.fd)
		io_closeclock(&pp->io);
	if (NULL != up)
		free(up);
}


/* 
 * unpack helpers
 */

static inline uint8_t
get_u8(
	const char *cp)
{
	return ((const u_char*)cp)[0];
}

static inline uint16_t
get_u16(
	const char *cp)
{
	return ((uint16_t)get_u8(cp) << 8) | get_u8(cp + 1);
}

/*
 * unpack & fix date (the receiver provides a valid time for 1024 weeks
 * after 1997-12-14 and therefore folds back in 2017, 2037,...)
 *
 * Returns -1 on error, day-of-month + (month * 32) othertwise.
 */
int
decode_date(
	struct refclockproc *pp,
	const char          *cp)
{
	static int32_t  s_baseday = 0;
	
	struct calendar jd;
	int32_t         rd;

	if (0 == s_baseday) {
		if (!ntpcal_get_build_date(&jd)) {
			jd.year     = 2015;
			jd.month    = 1;
			jd.monthday = 1;
		}
		s_baseday = ntpcal_date_to_rd(&jd);
	}

	/* get date fields and convert to RDN */
	jd.monthday = get_u8 (  cp  );
	jd.month    = get_u8 (cp + 1);
	jd.year     = get_u16(cp + 2);
	rd = ntpcal_date_to_rd(&jd);

	/* for the paranoid: do reverse calculation and cross-check */
	ntpcal_rd_to_date(&jd, rd);
	if ((jd.monthday != get_u8 (  cp  )) ||
	    (jd.month    != get_u8 (cp + 1)) ||
	    (jd.year     != get_u16(cp + 2))  )
		return - 1;
	
	/* calculate cycle shift to base day and calculate re-folded
	 * date
	 *
	 * One could do a proper modulo calculation here, but a counting
	 * loop is probably faster for the next few rollovers...
	 */
	while (rd < s_baseday)
		rd += 7*1024;
	ntpcal_rd_to_date(&jd, rd);

	/* fill refclock structure & indicate success */
	pp->day  = jd.yearday;
	pp->year = jd.year;	
	return ((int)jd.month << 5) | jd.monthday;
}
    

/* 
 * TSIP_decode - decode the TSIP data packets 
 */
int
TSIP_decode (
	struct peer *peer
	)
{
	int st;
	long   secint;
	double secs;
	double secfrac;
	unsigned short event = 0;
	int mmday;
	long tow;
	uint16_t wn;
	int GPS_UTC_Offset;
	
	struct palisade_unit *up;
	struct refclockproc *pp;

	pp = peer->procptr;
	up = pp->unitptr;

	/*
	 * Check the time packet, decode its contents. 
	 * If the timecode has invalid length or is not in
	 * proper format, declare bad format and exit.
	 */

	if ((up->type != CLK_THUNDERBOLT) &&
	    (up->type != CLK_ACUTIME    ) &&
	    (up->type != CLK_RESOLUTION ) &&
	    (up->type != CLK_ACE        ) &&
	    (up->type != CLK_COPERNICUS )   )
	{
		if ((up->rpt_buf[0] == (char) 0x41) ||
		    (up->rpt_buf[0] == (char) 0x46) ||
		    (up->rpt_buf[0] == (char) 0x54) ||
		    (up->rpt_buf[0] == (char) 0x4B) ||
		    (up->rpt_buf[0] == (char) 0x6D)) {

			/* standard time packet - GPS time and GPS week number */
#ifdef DEBUG
			printf("Palisade Port B packets detected. Connect to Port A\n");
#endif

			return 0;	
		}
	}

	/*
	 * We cast both to u_char as 0x8f uses the sign bit on a char
	 */
	if ((u_char) up->rpt_buf[0] == (u_char) 0x8f) {
		/* 
		 * Superpackets
		 */
		event = (unsigned short) (getint((u_char *) &mb(1)) & 0xffff);
		if (!((pp->sloppyclockflag & CLK_FLAG2) || event)) 
			/* Ignore Packet */
			return 0;	   
	
		switch (mb(0) & 0xff) {

		    case PACKET_8F0B: 

			if (up->polled <= 0)
				return 0;

			if (up->rpt_cnt != LENCODE_8F0B)  /* check length */
				break;
		
#ifdef DEBUG
			if (debug > 1) {
				int ts;
				double lat, lon, alt;
				lat = getdbl((u_char *) &mb(42)) * R2D;
				lon = getdbl((u_char *) &mb(50)) * R2D;
				alt = getdbl((u_char *) &mb(58));

				printf("TSIP_decode: unit %d: Latitude: %03.4f Longitude: %03.4f Alt: %05.2f m\n",
				       up->unit, lat,lon,alt);
				printf("TSIP_decode: unit %d: Sats:",
				       up->unit);
				for (st = 66, ts = 0; st <= 73; st++)
					if (mb(st)) {
						if (mb(st) > 0) ts++;
						printf(" %02d", mb(st));
					}
				printf(" : Tracking %d\n", ts); 
			}
#endif

			GPS_UTC_Offset = getint((u_char *) &mb(16));  
			if (GPS_UTC_Offset == 0) { /* Check UTC offset */ 
#ifdef DEBUG
				printf("TSIP_decode: UTC Offset Unknown\n");
#endif
				break;
			}

			secs = getdbl((u_char *) &mb(3));
			secint = (long) secs;
			secfrac = secs - secint; /* 0.0 <= secfrac < 1.0 */

			pp->nsec = (long) (secfrac * 1000000000); 

			secint %= 86400;    /* Only care about today */
			pp->hour = secint / 3600;
			secint %= 3600;
			pp->minute = secint / 60;
			secint %= 60;
			pp->second = secint % 60;

			mmday = decode_date(pp, &mb(11));
			if (mmday < 0)
				break;

#ifdef DEBUG
			if (debug > 1)
				printf("TSIP_decode: unit %d: %02X #%d %02d:%02d:%02d.%09ld %02d/%02d/%04d UTC %02d\n",
				       up->unit, mb(0) & 0xff, event, pp->hour, pp->minute, 
				       pp->second, pp->nsec, (mmday >> 5), (mmday & 31), pp->year, GPS_UTC_Offset);
#endif
			/* Only use this packet when no
			 * 8F-AD's are being received
			 */

			if (up->leap_status) {
				up->leap_status = 0;
				return 0;
			}

			return 2;
			break;

		    case PACKET_NTP:
			/* Palisade-NTP Packet */

			if (up->rpt_cnt != LENCODE_NTP) /* check length */
				break;
	
			up->leap_status = mb(19);

			if (up->polled  <= 0) 
				return 0;
				
			/* Check Tracking Status */
			st = mb(18);
			if (st < 0 || st > 14)
				st = 14;
			if ((st >= 2 && st <= 7) || st == 11 || st == 12) {
#ifdef DEBUG
				printf("TSIP_decode: Not Tracking Sats : %s\n",
				       *Tracking_Status[st]);
#endif
				refclock_report(peer, CEVNT_BADTIME);
				up->polled = -1;
				return 0;
				break;
			}

			mmday = decode_date(pp, &mb(14));
			if (mmday < 0)
				break;
			up->month  = (mmday >> 5);  /* Save for LEAP check */

			if ( (up->leap_status & PALISADE_LEAP_PENDING) &&
			/* Avoid early announce: https://bugs.ntp.org/2773 */
				(6 == up->month || 12 == up->month) ) {
				if (up->leap_status & PALISADE_UTC_TIME)  
					pp->leap = LEAP_ADDSECOND;
				else
					pp->leap = LEAP_DELSECOND;
			}
			else if (up->leap_status)
				pp->leap = LEAP_NOWARNING;
		
			else {  /* UTC flag is not set:
				 * Receiver may have been reset, and lost
				 * its UTC almanac data */
				pp->leap = LEAP_NOTINSYNC;
#ifdef DEBUG
				printf("TSIP_decode: UTC Almanac unavailable: %d\n",
				       mb(19));	
#endif
				refclock_report(peer, CEVNT_BADTIME);
				up->polled = -1;
				return 0;
			}

			pp->nsec = (long) (getdbl((u_char *) &mb(3))
					   * 1000000000);

			pp->hour = mb(11);
			pp->minute = mb(12);
			pp->second = mb(13);

#ifdef DEBUG
			if (debug > 1)
				printf("TSIP_decode: unit %d: %02X #%d %02d:%02d:%02d.%09ld %02d/%02d/%04d UTC %02x %s\n",
				       up->unit, mb(0) & 0xff, event, pp->hour, pp->minute, 
				       pp->second, pp->nsec, (mmday >> 5), (mmday & 31), pp->year,
				       mb(19), *Tracking_Status[st]);
#endif
			return 1;
			break;

		    case PACKET_8FAC:   
			if (up->polled <= 0)
				return 0; 

			if (up->rpt_cnt != LENCODE_8FAC)/* check length */
				break;

#ifdef DEBUG
			if (debug > 1) {
				double lat, lon, alt;
				lat = getdbl((u_char *) &mb(36)) * R2D;
				lon = getdbl((u_char *) &mb(44)) * R2D;
				alt = getdbl((u_char *) &mb(52));

				printf("TSIP_decode: unit %d: Latitude: %03.4f Longitude: %03.4f Alt: %05.2f m\n",
				       up->unit, lat,lon,alt);
				printf("TSIP_decode: unit %d\n", up->unit);
			}
#endif
			if ( (getint((u_char *) &mb(10)) & 0x80) &&
			/* Avoid early announce: https://bugs.ntp.org/2773 */
			    (6 == up->month || 12 == up->month) )
				pp->leap = LEAP_ADDSECOND;  /* we ASSUME addsecond */
			else 
				pp->leap = LEAP_NOWARNING;

#ifdef DEBUG
			if (debug > 1) 
				printf("TSIP_decode: unit %d: 0x%02x leap %d\n",
				       up->unit, mb(0) & 0xff, pp->leap);
			if (debug > 1) {
				printf("Receiver MODE: 0x%02X\n", (u_char)mb(1));
				if (mb(1) == 0x00)
					printf("                AUTOMATIC\n");
				if (mb(1) == 0x01)
					printf("                SINGLE SATELLITE\n");   
				if (mb(1) == 0x03)
					printf("                HORIZONTAL(2D)\n");
				if (mb(1) == 0x04)
					printf("                FULL POSITION(3D)\n");
				if (mb(1) == 0x05)
					printf("                DGPR REFERENCE\n");
				if (mb(1) == 0x06)
					printf("                CLOCK HOLD(2D)\n");
				if (mb(1) == 0x07)
					printf("                OVERDETERMINED CLOCK\n");

				printf("\n** Disciplining MODE 0x%02X:\n", (u_char)mb(2));
				if (mb(2) == 0x00)
					printf("                NORMAL\n");
				if (mb(2) == 0x01)
					printf("                POWER-UP\n");
				if (mb(2) == 0x02)
					printf("                AUTO HOLDOVER\n");
				if (mb(2) == 0x03)
					printf("                MANUAL HOLDOVER\n");
				if (mb(2) == 0x04)
					printf("                RECOVERY\n");
				if (mb(2) == 0x06)
					printf("                DISCIPLINING DISABLED\n");
			}
#endif   
			return 0;
			break;

		    case PACKET_8FAB:
			/* Thunderbolt Primary Timing Packet */

			if (up->rpt_cnt != LENCODE_8FAB) /* check length */
				break;

			if (up->polled  <= 0)
				return 0;

			GPS_UTC_Offset = getint((u_char *) &mb(7));

			if (GPS_UTC_Offset == 0){ /* Check UTC Offset */
#ifdef DEBUG
				printf("TSIP_decode: UTC Offset Unknown\n");
#endif
				break;
			}


			if ((mb(9) & 0x1d) == 0x0) {
				/* if we know the GPS time and the UTC offset,
				   we expect UTC timing information !!! */

				pp->leap = LEAP_NOTINSYNC;
				refclock_report(peer, CEVNT_BADTIME);
				up->polled = -1;
				return 0;
			}

			pp->nsec = 0;
#ifdef DEBUG		
			printf("\nTiming Flags are:\n");
			printf("Timing flag value is: 0x%X\n", mb(9));
			if ((mb(9) & 0x01) != 0)
				printf ("	Getting UTC time\n");
			else
				printf ("	Getting GPS time\n");
			if ((mb(9) & 0x02) != 0)
				printf ("	PPS is from UTC\n");
			else
				printf ("	PPS is from GPS\n");
			if ((mb(9) & 0x04) != 0)
				printf ("	Time is not Set\n");
			else
				printf ("	Time is Set\n");
			if ((mb(9) & 0x08) != 0)
				printf("	I dont have UTC info\n");
			else
				printf ("	I have UTC info\n");
			if ((mb(9) & 0x10) != 0)
				printf ("	Time is from USER\n\n");
			else
				printf ("	Time is from GPS\n\n");	
#endif		

			mmday = decode_date(pp, &mb(13));
			if (mmday < 0)
				break;
			tow = getlong((u_char *) &mb(1));
#ifdef DEBUG		
			if (debug > 1) {
				printf("pp->day: %d\n", pp->day); 
				printf("TOW: %ld\n", tow);
				printf("DAY: %d\n", (mmday & 31));
			}
#endif
			pp->hour = mb(12);
			pp->minute = mb(11);
			pp->second = mb(10);


#ifdef DEBUG
			if (debug > 1)
				printf("TSIP_decode: unit %d: %02X #%d %02d:%02d:%02d.%09ld %02d/%02d/%04d ",
				       up->unit, mb(0) & 0xff, event, pp->hour, pp->minute, pp->second,
				       pp->nsec, (mmday >> 5), (mmday & 31), pp->year);
#endif
			return 1;
			break;

		    default:
			/* Ignore Packet */
			return 0;
		} /* switch */
	} /* if 8F packets */

	else if (up->rpt_buf[0] == (u_char)0x42) {
		printf("0x42\n");
		return 0;
	}
	else if (up->rpt_buf[0] == (u_char)0x43) {
		printf("0x43\n");
		return 0;
	}
	else if ((up->rpt_buf[0] == PACKET_41) & (up->type == CLK_THUNDERBOLT)){
		printf("Undocumented 0x41 packet on Thunderbolt\n");
		return 0;
	}
	else if ((up->rpt_buf[0] == PACKET_41A) & (up->type == CLK_ACUTIME)) {
#ifdef DEBUG
		printf("GPS TOW: %ld\n", (long)getlong((u_char *) &mb(0)));
		printf("GPS WN: %d\n", getint((u_char *) &mb(4)));
		printf("GPS UTC-GPS Offset: %ld\n", (long)getlong((u_char *) &mb(6)));
#endif
		return 0;
	}

	/* GPS time packet for ACE III or Copernicus II receiver */
	else if ((up->rpt_buf[0] == PACKET_41) &&
	         ((up->type == CLK_ACE) || (up->type == CLK_COPERNICUS))) {
#ifdef DEBUG
		if ((debug > 1) && (up->type == CLK_ACE))
			printf("TSIP_decode: Packet 0x41 seen in ACE III mode\n");
		if ((debug > 1) && (up->type == CLK_COPERNICUS))
			printf("TSIP_decode: Packet 0x41 seen in Copernicus II mode\n");
#endif
		if (up->rpt_cnt != LENCODE_41) { /* check length */
			refclock_report(peer, CEVNT_BADREPLY);
			up->polled = -1;
#ifdef DEBUG
			printf("TSIP_decode: unit %d: bad packet %02x len %d\n", 
				up->unit, up->rpt_buf[0] & 0xff, up->rpt_cnt);
#endif
			return 0;
		}
		if (up->polled  <= 0)
			return 0;
		tow = (long)getsingle((u_char *) &mb(0));
		wn = (uint16_t)getint((u_char *) &mb(4));
		GPS_UTC_Offset = (int)getsingle((u_char *) &mb(6));
		if (GPS_UTC_Offset == 0){ /* Check UTC Offset */
#ifdef DEBUG
			printf("TSIP_decode: UTC Offset Unknown\n");
#endif
			refclock_report(peer, CEVNT_BADREPLY);
			up->polled = -1;
			return 0;
		}
		/* Get date & time from WN & ToW minus offset */
		{
			TCivilDate cd;
			TGpsDatum wd;
			l_fp ugo; /* UTC-GPS offset, negative number */
			ugo.Ul_i.Xl_i = (int32_t)-GPS_UTC_Offset;
			ugo.l_uf = 0;
			wd = gpscal_from_gpsweek((wn % 1024), (int32_t)tow, ugo);
			gpscal_to_calendar(&cd, &wd);
			pp->year = cd.year;
			pp->day = cd.yearday;
			pp->hour = cd.hour;
			pp->minute = cd.minute;
			pp->second = cd.second;
			pp->nsec = 0;
			pp->leap = LEAP_NOWARNING;
#ifdef DEBUG
			if (debug > 1)	{
				printf("GPS TOW: %ld\n", tow);
				printf("GPS WN: %d\n", wn);
				printf("GPS UTC-GPS Offset: %d\n", GPS_UTC_Offset);
				printf("TSIP_decode: unit %d: %02X #%d %02d:%02d:%02d.%09ld %02d/%02d/%04d ",
				       up->unit, mb(0) & 0xff, event, pp->hour, pp->minute, pp->second,
				       pp->nsec, cd.month, cd.monthday, pp->year);
			}
#endif
		}
		return 1;
	}

	/* Health Status for Acutime Receiver */
	else if ((up->rpt_buf[0] == PACKET_46) & (up->type == CLK_ACUTIME)) {
#ifdef DEBUG
		if (debug > 1)
		/* Status Codes */
			switch (mb(0)) {
			    case 0x00:
				printf ("Doing Position Fixes\n");
				break;
			    case 0x01:
				printf ("Do not have GPS time yet\n");
				break;
			    case 0x03:
				printf ("PDOP is too high\n");
				break;
			    case 0x08:
				printf ("No usable satellites\n");
				break;
			    case 0x09:
				printf ("Only 1 usable satellite\n");
				break;
			    case 0x0A:
				printf ("Only 2 usable satellites\n");
				break;
			    case 0x0B:
				printf ("Only 3 usable satellites\n");
				break;
			    case 0x0C:
				printf("The Chosen satellite is unusable\n");
				break;
			}
#endif
		/* Error Codes */
		if (mb(1) != 0)	{
			
			refclock_report(peer, CEVNT_BADTIME);
			up->polled = -1;
#ifdef DEBUG
			if (debug > 1) {
				if (mb(1) & 0x01)
					printf ("Signal Processor Error, reset unit.\n");
				if (mb(1) & 0x02)
					printf ("Alignment error, channel or chip 1, reset unit.\n");
				if (mb(1) & 0x03)
					printf ("Alignment error, channel or chip 2, reset unit.\n");
				if (mb(1) & 0x04)
					printf ("Antenna feed line fault (open or short)\n");
				if (mb(1) & 0x05)
					printf ("Excessive reference frequency error, refer to packet 0x2D and packet 0x4D documentation for further information\n");
			}
#endif
		
		return 0;
		}
	}

	/* Health Status for Copernicus II Receiver */
	else if ((up->rpt_buf[0] == PACKET_46) && (up->type == CLK_COPERNICUS)) {
#ifdef DEBUG
		if (debug > 1)
		/* Status Codes */
			switch (mb(0)) {
			    case 0x00:
				printf ("Doing Position Fixes\n");
				break;
			    case 0x01:
				printf ("Do not have GPS time yet\n");
				break;
			    case 0x03:
				printf ("PDOP is too high\n");
				break;
			    case 0x04:
				printf("The Chosen satellite is unusable\n");
				break;
			    case 0x08:
				printf ("No usable satellites\n");
				break;
			    case 0x09:
				printf ("Only 1 usable satellite\n");
				break;
			    case 0x0A:
				printf ("Only 2 usable satellites\n");
				break;
			    case 0x0B:
				printf ("Only 3 usable satellites\n");
				break;
			}
#endif
		/* Error Codes */
		if ((mb(1) & 0x3E) != 0) {  /* Don't regard bits 0 and 6 as errors */
			refclock_report(peer, CEVNT_BADTIME);
			up->polled = -1;
#ifdef DEBUG
			if (debug > 1) {
				if ((mb(1) & 0x18) == 0x08)
					printf ("Antenna feed line fault (open)\n");
				if ((mb(1) & 0x18) == 0x18)
					printf ("Antenna feed line fault (short)\n");
			}
#endif
		}
		return 0;
	}

	/* Other packets output by ACE III & Copernicus II Receivers, dropped silently */
	else if (((up->rpt_buf[0] == (char) 0x4A) ||
		  (up->rpt_buf[0] == (char) 0x4B) ||
		  (up->rpt_buf[0] == (char) 0x56) ||
		  (up->rpt_buf[0] == (char) 0x5F) ||
		  (up->rpt_buf[0] == (char) 0x6D) ||
		  (up->rpt_buf[0] == (char) 0x82) ||
		  (up->rpt_buf[0] == (char) 0x84)) &&
		 ((up->type == CLK_ACE) || (up->type == CLK_COPERNICUS))) {
#ifdef DEBUG
		if ((debug > 1) && (up->type == CLK_ACE))
			printf("TSIP_decode: Packet 0x%2x seen in ACE III mode\n", (up->rpt_buf[0] & 0XFF));
		if ((debug > 1) && (up->type == CLK_COPERNICUS))
			printf("TSIP_decode: Packet 0x%2x seen in Copernicus II mode\n", (up->rpt_buf[0] & 0XFF));
#endif
		return 0;
	}

	else if (up->rpt_buf[0] == 0x54)
		return 0;

	else if (up->rpt_buf[0] == PACKET_6D) {
#ifdef DEBUG
		int sats;

		if ((mb(0) & 0x01) && (mb(0) & 0x02))
			printf("2d Fix Dimension\n");
		if (mb(0) & 0x04)
			printf("3d Fix Dimension\n");

		if (mb(0) & 0x08)
			printf("Fix Mode is MANUAL\n");
		else
			printf("Fix Mode is AUTO\n");
	
		sats = mb(0) & 0xF0;
		sats = sats >> 4;
		printf("Tracking %d Satellites\n", sats);
#endif
		return 0;
	} /* else if not super packet */
	refclock_report(peer, CEVNT_BADREPLY);
	up->polled = -1;
#ifdef DEBUG
	printf("TSIP_decode: unit %d: bad packet %02x-%02x event %d len %d\n", 
	       up->unit, up->rpt_buf[0] & 0xff, mb(0) & 0xff, 
	       event, up->rpt_cnt);
#endif
	return 0;
}

/*
 * palisade__receive - receive data from the serial interface
 */

static void
palisade_receive (
	struct peer * peer
	)
{
	struct palisade_unit *up;
	struct refclockproc *pp;

	/*
	 * Initialize pointers and read the timecode and timestamp.
	 */
	pp = peer->procptr;
	up = pp->unitptr;
		
	if (! TSIP_decode(peer)) return;
	
	if (up->polled <= 0) 
		return;   /* no poll pending, already received or timeout */

	up->polled = 0;  /* Poll reply received */
	pp->lencode = 0; /* clear time code */
#ifdef DEBUG
	if (debug) 
		printf(
			"palisade_receive: unit %d: %4d %03d %02d:%02d:%02d.%09ld\n",
			up->unit, pp->year, pp->day, pp->hour, pp->minute, 
			pp->second, pp->nsec);
#endif

	/*
	 * Process the sample
	 * Generate timecode: YYYY DoY HH:MM:SS.microsec 
	 * report and process 
	 */

	snprintf(pp->a_lastcode, sizeof(pp->a_lastcode),
		 "%4d %03d %02d:%02d:%02d.%09ld",
		 pp->year, pp->day,
		 pp->hour,pp->minute, pp->second, pp->nsec); 
	pp->lencode = 24;

	if (!refclock_process(pp)) {
		refclock_report(peer, CEVNT_BADTIME);

#ifdef DEBUG
		printf("palisade_receive: unit %d: refclock_process failed!\n",
		       up->unit);
#endif
		return;
	}

	record_clock_stats(&peer->srcadr, pp->a_lastcode); 

#ifdef DEBUG
	if (debug)
		printf("palisade_receive: unit %d: %s\n",
		       up->unit, prettydate(&pp->lastrec));
#endif
	pp->lastref = pp->lastrec;
	refclock_receive(peer);
}


/*
 * palisade_poll - called by the transmit procedure
 *
 */
static void
palisade_poll (
	int unit,
	struct peer *peer
	)
{
	struct palisade_unit *up;
	struct refclockproc *pp;
	
	pp = peer->procptr;
	up = pp->unitptr;

	pp->polls++;
	if (up->polled > 0) /* last reply never arrived or error */ 
		refclock_report(peer, CEVNT_TIMEOUT);

	up->polled = 2; /* synchronous packet + 1 event */
	
#ifdef DEBUG
	if (debug)
		printf("palisade_poll: unit %d: polling %s\n", unit,
		       (pp->sloppyclockflag & CLK_FLAG2) ? 
		       "synchronous packet" : "event");
#endif 

	if (pp->sloppyclockflag & CLK_FLAG2) 
		return;  /* using synchronous packet input */

	if(up->type == CLK_PRAECIS) {
		if (write(peer->procptr->io.fd,"SPSTAT\r\n",8) < 0) {
			msyslog(LOG_ERR, "Palisade(%d) write: %m:",unit);
		} else {
			praecis_msg = 1;
			return;
		}
	}

	if (HW_poll(pp) < 0) 
		refclock_report(peer, CEVNT_FAULT); 
}

static void
praecis_parse (
	struct recvbuf *rbufp,
	struct peer *peer
	)
{
	static char buf[100];
	static int p = 0;
	struct refclockproc *pp;

	pp = peer->procptr;

	if (p + rbufp->recv_length >= sizeof buf) {
		struct palisade_unit *up;
		up = pp->unitptr;

		/*
		 * We COULD see if there is a \r\n in the incoming
		 * buffer before it overflows, and then process the
		 * current line.
		 *
		 * Similarly, if we already have a hunk of data that
		 * we're now flushing, that will cause the line of
		 * data we're in the process of collecting to be garbage.
		 *
		 * Since we now check for this overflow and log when it
		 * happens, we're now in a better place to easily see
		 * what's going on and perhaps better choices can be made.
		 */

		/* Do we need to log the size of the overflow? */
		msyslog(LOG_ERR, "Palisade(%d) praecis_parse(): input buffer overflow", 
			up->unit);

		p = 0;
		praecis_msg = 0;

		refclock_report(peer, CEVNT_BADREPLY);

		return;
	}

	memcpy(buf+p, rbufp->recv_buffer, rbufp->recv_length);
	p += rbufp->recv_length;

	if (   p >= 2
	    && buf[p-2] == '\r' 
	    && buf[p-1] == '\n') {
		buf[p-2] = '\0';
		record_clock_stats(&peer->srcadr, buf);

		p = 0;
		praecis_msg = 0;

		if (HW_poll(pp) < 0) {
			refclock_report(peer, CEVNT_FAULT);
		}
	}
	return;
}

static void
palisade_io (
	struct recvbuf *rbufp
	)
{
	/*
	 * Initialize pointers and read the timecode and timestamp.
	 */
	struct palisade_unit *up;
	struct refclockproc *pp;
	struct peer *peer;

	char * c, * d;

	peer = rbufp->recv_peer;
	pp = peer->procptr;
	up = pp->unitptr;

	if(up->type == CLK_PRAECIS) {
		if(praecis_msg) {
			praecis_parse(rbufp,peer);
			return;
		}
	}

	c = (char *) &rbufp->recv_space;
	d = c + rbufp->recv_length;
		
	while (c != d) {

		/* Build time packet */
		switch (up->rpt_status) {

		    case TSIP_PARSED_DLE_1:
			switch (*c)
			{
			    case 0:
			    case DLE:
			    case ETX:
				up->rpt_status = TSIP_PARSED_EMPTY;
				break;

			    default:
				up->rpt_status = TSIP_PARSED_DATA;
				/* save packet ID */
				up->rpt_buf[0] = *c;
				break;
			}
			break;

		    case TSIP_PARSED_DATA:
			if (*c == DLE)
				up->rpt_status = TSIP_PARSED_DLE_2;
			else 
				mb(up->rpt_cnt++) = *c;
			break;

		    case TSIP_PARSED_DLE_2:
			if (*c == DLE) {
				up->rpt_status = TSIP_PARSED_DATA;
				mb(up->rpt_cnt++) = 
				    *c;
			}
			else if (*c == ETX) 
				up->rpt_status = TSIP_PARSED_FULL;
			else 	{
				/* error: start new report packet */
				up->rpt_status = TSIP_PARSED_DLE_1;
				up->rpt_buf[0] = *c;
			}
			break;

		    case TSIP_PARSED_FULL:
		    case TSIP_PARSED_EMPTY:
		    default:
			if ( *c != DLE)
				up->rpt_status = TSIP_PARSED_EMPTY;
			else 
				up->rpt_status = TSIP_PARSED_DLE_1;
			break;
		}
		
		c++;

		if (up->rpt_status == TSIP_PARSED_DLE_1) {
			up->rpt_cnt = 0;
			if (pp->sloppyclockflag & CLK_FLAG2) 
				/* stamp it */
				get_systime(&pp->lastrec);
		}
		else if (up->rpt_status == TSIP_PARSED_EMPTY)
			up->rpt_cnt = 0;

		else if (up->rpt_cnt > BMAX) 
			up->rpt_status =TSIP_PARSED_EMPTY;

		if (up->rpt_status == TSIP_PARSED_FULL) 
			palisade_receive(peer);

	} /* while chars in buffer */
}


/*
 * Trigger the Palisade's event input, which is driven off the RTS
 *
 * Take a system time stamp to match the GPS time stamp.
 *
 */
long
HW_poll (
	struct refclockproc * pp 	/* pointer to unit structure */
	)
{	
	int x;	/* state before & after RTS set */
	struct palisade_unit *up;
	struct packettx tx;

	up = pp->unitptr;

	if (up->type == CLK_ACE) {
		/* Poll by sending a 0x21 command */
		tx.size = 0;
		tx.data = (u_char *) emalloc(100);
		sendcmd (&tx, 0x21);
		sendetx (&tx, pp->io.fd);
		free(tx.data);
	} else {

	/* read the current status, so we put things back right */
	if (ioctl(pp->io.fd, TIOCMGET, &x) < 0) {
		DPRINTF(1, ("Palisade HW_poll: unit %d: GET %m\n",
			up->unit));
		msyslog(LOG_ERR, "Palisade(%d) HW_poll: ioctl(fd,GET): %m", 
			up->unit);
		return -1;
	}
  
	x |= TIOCM_RTS;        /* turn on RTS  */

	/* Edge trigger */
	if (up->type == CLK_ACUTIME)
		if (write (pp->io.fd, "", 1) != 1)
			msyslog(LOG_WARNING,
				"Palisade(%d) HW_poll: failed to send trigger: %m", 
				up->unit);
		
	if (ioctl(pp->io.fd, TIOCMSET, &x) < 0) { 
#ifdef DEBUG
		if (debug)
			printf("Palisade HW_poll: unit %d: SET \n", up->unit);
#endif
		msyslog(LOG_ERR,
			"Palisade(%d) HW_poll: ioctl(fd, SET, RTS_on): %m", 
			up->unit);
		return -1;
	}

	x &= ~TIOCM_RTS;        /* turn off RTS  */
	
	} /* (up->type != CLK_ACE) */

	/* poll timestamp */
	get_systime(&pp->lastrec);

	if (up->type != CLK_ACE) {
	if (ioctl(pp->io.fd, TIOCMSET, &x) == -1) {
#ifdef DEBUG
		if (debug)
			printf("Palisade HW_poll: unit %d: UNSET \n", up->unit);
#endif
		msyslog(LOG_ERR,
			"Palisade(%d) HW_poll: ioctl(fd, UNSET, RTS_off): %m", 
			up->unit);
		return -1;
	}
	}

	return 0;
}

/*
 * copy/swap a big-endian palisade double into a host double
 */
static double
getdbl (
	u_char *bp
	)
{
#ifdef WORDS_BIGENDIAN
	double out;

	memcpy(&out, bp, sizeof(out));
	return out;
#else
	union {
		u_char ch[8];
		u_int32 u32[2];
	} ui;
		
	union {
		double out;
		u_int32 u32[2];
	} uo;

	memcpy(ui.ch, bp, sizeof(ui.ch));
	/* least-significant 32 bits of double from swapped bp[4] to bp[7] */
	uo.u32[0] = ntohl(ui.u32[1]);
	/* most-significant 32 bits from swapped bp[0] to bp[3] */
	uo.u32[1] = ntohl(ui.u32[0]);

	return uo.out;
#endif
}

/*
 * copy/swap a big-endian palisade short into a host short
 */
static short
getint (
	u_char *bp
	)
{
	u_short us;

	memcpy(&us, bp, sizeof(us));
	return (short)ntohs(us);
}

/*
 * copy/swap a big-endian palisade 32-bit int into a host 32-bit int
 */
static int32
getlong(
	u_char *bp
	)
{
	u_int32 u32;

	memcpy(&u32, bp, sizeof(u32));
	return (int32)(u_int32)ntohl(u32);
}

/*
 * copy/swap a big-endian 32-bit single-precision floating point into a host 32-bit int
 */
static int32
getsingle(
	u_char *bp
	)
{
	u_int32 mantissa;
	int8_t exponent;
	uint8_t sign, exp_field;
	int32 res;

	memcpy(&mantissa, bp, sizeof(mantissa));
	mantissa = ((u_int32)ntohl(mantissa) & 0x7FFFFF) | 0x800000;
	exp_field = ((uint8_t)bp[0] << 1) + ((uint8_t)bp[1] >> 7);
	exponent = (int8_t)exp_field - 127;
	sign = ((uint8_t)bp[0] >> 7);
	if (exponent > 23)
		res = (int32)(mantissa << (exponent - 23));
	else
		res = (int32)(mantissa >> (23 - exponent));
	return sign ? -res : res;
}

#else	/* REFCLOCK && CLOCK_PALISADE*/
NONEMPTY_TRANSLATION_UNIT
#endif
