/*
 * refclock_pst - driver for the PSTI 1010/1020 WWV clock
 */
#if defined(REFCLOCK) && (defined(PST) || defined(PSTCLK) || defined(PSTPPS))

#include <stdio.h>
#include <ctype.h>
#include <sys/types.h>
#include <sys/time.h>
#include <sys/file.h>
#include <fcntl.h>

#include "ntpd.h"
#include "ntp_refclock.h"
#include "ntp_unixtime.h"

#if defined(BSD_TTYS)
#include <sgtty.h>
#endif /* BSD_TTYS */

#if defined(SYSV_TTYS)
#include <termio.h>
#endif /* SYSV_TTYS */

#if defined(STREAM)
#include <termios.h>
#include <stropts.h>
#if defined(PSTCLK)
#include <sys/clkdefs.h>
#endif /* PSTCLK */
#endif /* STREAM */

#if defined (PSTPPS)
#include <sys/ppsclock.h>
#endif /* PSTPPS */

/*
 * This driver is in good measure due to David Schachter, who wrote
 * the firmware for the PST clock.  Not that he is to blame for
 * any of this, but he kindly loaned me a clock to allow me to
 * debug this.
 *
 * Postscript:
 *
 * The strategy in here is actually pretty good, especially if
 * you try to support the clock on something lacking low order
 * clock bits like a Sun, since all the business which is done
 * before taking a time stamp tends to randomize the taking of
 * the stamp with respect to the timer interrupt.  It is, however,
 * a big cpu hog, and in some ways is a bit of a waste since, as
 * it turns out, the PST clock can give you no better than a
 * millisecond precision and it doesn't pay to try to push it
 * harder.
 *
 * In any event, like the first waffle off the iron, this one
 * should probably be tossed.  My current preference would be
 * to retain the 12-a-minute schedule, but to use the QU command
 * instead of the QD and QT, and to only send a QM command with
 * the 12th poll of the minute to get the minutes-since-sync
 * and the station.  Need to get a clock which supports QU,
 * however.
 *
 * End postscript
 *
 * This driver polls the clock using the QM, QT and QD commands.
 * Ntpd actually uses QU instead of the last two, something I would
 * like to have done as well since it gives you the day and time
 * atom, but the firmware in the clock I had (X04.01.999) didn't know
 * about this command.
 *
 * The QM command produces output like:
 *
 *	O6B532352823C00270322
 *	   b     c  deeee
 *
 * We use (b) for the time zone, (c) to see whether time is available,
 * (d) to tell whether we are sync'd to WWV or WWVH, and (e) to determine
 * the number of minutes since the last signal was received.  We
 * don't trust the clock for more than about 20 minutes on its own.
 * After this, we keep taking the time but mark the clock unsynchronized.
 *
 * The QT command returns something that looks like this:
 *
 *	18:57:50.263D
 *
 * Note that this particular sample is in 24 hour format, local time
 * (daylight savings time even).  We allow just about anything for
 * this (sigh) since this leaves the clock owner free to set the
 * display mode in whatever way he finds convenient for setting
 * his watch.
 *
 * The QD command returns:
 *
 *	89/10/19/292
 *
 * We actually only use the day-of-the-year here.  We use the year
 * only to determine whether the PST clock thinks the current year
 * has 365 or 366 days in it.
 *
 * At the current writing, this code expects to be using a BSD-style
 * terminal driver.  It will compile code which uses the CLKLDISC
 * line discipline if it thinks this is available, but use cooked
 * mode otherwise.  The cooked mode stuff may not have been tested.
 */

/*
 * Definitions
 */
#define	MAXUNITS	4	/* maximum number of PST units permitted */
#define	PSTDEV	"/dev/pst%d"	/* device we open.  %d is unit number */
#define	NPSTSAMPS	12	/* take 12 PST samples per minute */

/*
 * Other constant stuff
 */
#define	PSTPRECISION	(-9)		/* what the heck */
#define	WWVREFID	"WWV\0"
#define	WWVHREFID	"WWVH"
#define	PSTHSREFID	0x7f7f030a	/* 127.127.3.10 refid for hi strata */

/*
 * Parameters for the clock
 */
#define	SPEED232	B9600
#define	PSTMAGIC2	('\r' | 0x80)	/* HP-UX uses this also now */
#ifdef CLKLDISC
#define	PSTMAGIC1	'\r'
#define	PSTEOL		'\r'
#else
#define	PSTEOL		'\n'
#endif

/*
 * Description of clock.  We fill in whether it is a 1010 or 1020,
 * and the firmware revision, using the QV command.
 */
#define	PSTDESCLEN	64
#define	PSTDESCRIPTION	"%s %s (%s) WWV/H Receiver"
#define	PSTDEFDESC	"PSTI/Traconex 10?0 (V??.??) WWV/H Receiver"

/*
 * Length of the PST time code.  This must be the length of the output
 * of the QM command, plus QT, plus QD, plus two spaces.  We make it
 * big just on principle.
 */
#define	PSTCODELEN	(128)

/*
 * Minimum and maximum lengths
 */
#define	PSTMINQVLEN	(16)
#define	PSTMAXQVLEN	(24)

#define	PSTMINQMLEN	(19)
#define	PSTMAXQMLEN	(32)

#define	PSTMINQDLEN	(12)
#define	PSTMAXQDLEN	(12)

#define	PSTMINQTLEN	(14)
#define	PSTMAXQTLEN	(14)

/*
 * It turns out that the QT command does *not* adjust for transmission
 * delays.  Since the QT command returns 15 characters at 9600 baud,
 * the adjustment for this should be 15.6 ms.  We'll default to this,
 * but don't let this stop you from fiddling with the fudge factors
 * to make things come out right
 */
#define	PSTQTFUDGE	0x04000000	/* about 15.6 ms */

/*
 * Default propagation delays.  About right for Toronto
 */
#define	DEFWWVPROP	0x01eb851f	/* about 7.5 ms */
#define	DEFWWVHPROP	0x06c8b439	/* about 26.5 ms */

/*
 * Maximum propagation delay we believe.  125 ms as an l_fp fraction
 */
#define	PSTMAXPROP	0x20000000

/*
 * Default minutes since an update.
 */
#define	DEFMAXFREERUN	(20)

/*
 * Hack to avoid excercising the multiplier.  I have no pride.
 */
#define	MULBY10(x)	(((x)<<3) + ((x)<<1))

/*
 * PST unit control structure.
 */
struct pstunit {
	struct peer *peer;		/* associated peer structure */
	struct event psttimer;		/* timeout timer structure */
	struct refclockio pstio;	/* given to the I/O handler */
	l_fp rectimes[NPSTSAMPS];	/* times we received this stuff */
	l_fp reftimes[NPSTSAMPS];	/* times of codes received */
	l_fp lastrec;			/* last receive time */
	l_fp lastref;			/* last reference time */
	char description[PSTDESCLEN];	/* description of clock */
	char lastcode[PSTCODELEN];	/* last code we received */
	u_char lencode;			/* length of the last code */
	u_char nextsample;		/* the next offset expected */
	u_char unit;			/* unit number for this guy */
	u_char state;			/* what we're waiting for */
	s_char station;			/* WWV or WWVH? */
	u_char flags;			/* flag byte */
	u_char status;			/* clock status */
	u_char lastevent;		/* last clock event */
	u_char timezone;		/* hour offset to time zone */
	u_char errors;			/* number of errors detected */
	u_char year;			/* year reported by clock */
	u_char month;			/* month, from clock */
	u_char monthday;		/* day, from clock */
	u_char hour;			/* hour of day */
	u_char minute;			/* minute of day */
	u_char second;			/* second of day */
	u_char leap;			/* leap indicators */
	s_char tzoffset;		/* time zone offset */
	u_char reason;			/* reason for failure */
	u_short millisecond;		/* millisecond of day */
	u_short yearday;		/* day of the year */
	u_short timesincesync;		/* time since radio got sample */
	U_LONG yearstart;		/* NTP time at year start */
	U_LONG lastupdate;		/* last time data received */
	U_LONG polls;			/* number of polls */
	U_LONG noreply;			/* number of time outs */
	U_LONG badformat;		/* number of bad format responses */
	U_LONG baddata;			/* number of invalid time codes */
	U_LONG timestarted;		/* time we started this */
};

/*
 * States we might be in
 */
#define	STATE_IDLE	0		/* not doing anything in particular */
#define	STATE_QV	1		/* trying to get version */
#define	STATE_QM	2		/* sent QM */
#define	STATE_QD	3		/* sent QD */
#define	STATE_QT	4		/* send QT */

/*
 * Status flags
 */
#define	PST_LEAPYEAR	0x1		/* pst clock thinks it is a leap year */
#define	PST_SIGFAULT	0x2		/* signal fault */
#define	PST_HARDERR	0x4		/* hardware error */
#define	PST_NOTIME	0x8		/* no time available */
#define	PST_WWVH	0x10		/* synchronized to WWVH */
#define	PST_DOQV	0x20		/* get version, reinit delays */
#define	PST_DORESET	0x40		/* reset the clock */

/*
 * The PST often encodes stuff by adding an ASCII '0' to it.  The
 * largest range of values encoded this way is 0 through 31, or '0'
 * through 'O'.  These macroes manipulate these values.
 */
#define	ISVALIDPST(c)	((c) >= '0' && (c) <= 'O')
#define	PSTTOBIN(c)	((int)(c) - '0')
#define	BINTOPST(c)	((char)((c) + '0'))

/*
 * Status bits.  Look at the QM command
 */
#define	SIGFAULT	0x1
#define	HARDFAULT	0x2
#define	OUTOFSPEC	0x4
#define	TIMEAVAILABLE	0x8

/*
 * Module reason codes
 */
#define	QVREASON	20
#define	QMREASON	40
#define	QDREASON	60
#define	QTREASON	80

/*
 * Station i.d. characters in QM output
 */
#define	WWV_CHAR	'C'
#define	WWVH_CHAR	'H'

/*
 * We allow a few errors, but if we get more than 12 seconds behind
 * the schedule we start from sample 0 again.  4 seconds is the minimum
 * time between time out routine executions.
 */
#define	PSTMAXDELAY	12
#define	PSTMINTIMEOUT	4

/*
 * The PST polling schedule.  We poll 12 times per 64 seconds (far too
 * many, but what the heck).  The polls are scheduled to finish in this
 * time with the assumption that the timer is good for no better than
 * 4 second resolution.  If we get too far behind (due to bad samples
 * or no responses) we start over.
 */
struct pstsched {
	u_short nextinterval;
	u_short tooold;
};

static struct pstsched psttab[NPSTSAMPS] = {
	{ 4,	PSTMAXDELAY+1 },
	{ 4,	PSTMAXDELAY+1+4 },
	{ 8,	PSTMAXDELAY+1+4+4 },
	{ 4,	PSTMAXDELAY+1+4+4+8 },
	{ 8,	PSTMAXDELAY+1+4+4+8+4 },
	{ 4,	PSTMAXDELAY+1+4+4+8+4+8 },
	{ 4,	PSTMAXDELAY+1+4+4+8+4+8+4 },
	{ 8,	PSTMAXDELAY+1+4+4+8+4+8+4+4 },
	{ 4,	PSTMAXDELAY+1+4+4+8+4+8+4+4+8 },
	{ 8,	PSTMAXDELAY+1+4+4+8+4+8+4+4+8+4 },
	{ 4,	PSTMAXDELAY+1+4+4+8+4+8+4+4+8+4+8 },
	{ 4,	PSTMAXDELAY+1+4+4+8+4+8+4+4+8+4+8+4 }
};


/*
 * Data space for the unit structures.  Note that we allocate these on
 * the fly, but never give them back.
 */
static struct pstunit *pstunits[MAXUNITS];
static u_char unitinuse[MAXUNITS];

/*
 * Structure to keep processed propagation data in.
 */
struct pst_propagate {
	U_LONG remainder;	/* left over submillisecond remainder */
	char msbchar;		/* character for high order bits */
	char lsbchar;		/* character for low order bits */
};


/*
 * Keep the fudge factors separately so they can be set even
 * when no clock is configured.
 */
static l_fp wwv_prop_delay[MAXUNITS];
static l_fp wwvh_prop_delay[MAXUNITS];
static struct pst_propagate wwv_prop_data[MAXUNITS];
static struct pst_propagate wwvh_prop_data[MAXUNITS];
static u_char stratumtouse[MAXUNITS];
static u_char sloppyclock[MAXUNITS];
static u_short freerun[MAXUNITS];

/*
 * Pointer to the default description
 */
static char *pstdefdesc = PSTDEFDESC;

/*
 * macro for writing to the clock, printing an error if we fail
 */
#define	pst_send(pst, str, len)		\
	if (write((pst)->pstio.fd, (str), (len)) < 0) \
		pst_write_error((pst))

/*
 * macro for resetting the clock structure to zero
 */
#define	pst_reset(pst) \
	do { \
		pst->nextsample = 0; \
		pst->station = 0; \
		pst->leap = 0; \
	} while (0)

/*
 * macro for event reporting
 */
#define	pst_event(pst, evnt_code) \
	do { \
		if ((pst)->status != (u_char)(evnt_code)) \
			pst_do_event((pst), (evnt_code)); \
	} while (0)

/*
 * Imported from the timer module
 */
extern U_LONG current_time;
extern struct event timerqueue[];

/*
 * Imported from ntp_loopfilter module
 */
extern int fdpps;		/* pps file descriptor */

/*
 * Imported from ntpd module
 */
extern int debug;		/* global debug flag */

/*
 * Function prototypes
 */
static	void	pst_init	P((void));
static	int	pst_start	P((u_int, struct peer *));
static	void	pst_shutdown	P((int));
static	void	pst_receive	P((struct recvbuf *));
static	void	pst_process	P((struct pstunit *));
static	void	pst_control	P((u_int, struct refclockstat *, struct refclockstat *));
static	void	pst_buginfo	P((int, struct refclockbug *));
static	void	pst_write_error	P((struct pstunit *));
static	void	pst_timeout	P((struct peer *));
static	int	pst_QV_process	P((struct pstunit *, struct recvbuf *));
static	int	pst_QM_process	P((struct pstunit *, struct recvbuf *));
static	int	pst_QD_process	P((struct pstunit *, struct recvbuf *));
static	int	pst_QT_process	P((struct pstunit *, struct recvbuf *, l_fp *, l_fp *));
static	void	pst_do_event	P((struct pstunit *, int));
static	void	pst_compute_delay	P((U_LONG, struct pst_propagate *));

/*
 * Transfer vector
 */
struct	refclock refclock_pst = {
	pst_start, pst_shutdown, noentry,
	pst_control, pst_init, pst_buginfo, NOFLAGS
};

/*
 * pst_init - initialize internal PST driver data
 */
static void
pst_init()
{
	register int i;

	/*
	 * Just zero the data arrays
	 */
	bzero((char *)pstunits, sizeof pstunits);
	bzero((char *)unitinuse, sizeof unitinuse);

	/*
	 * Initialize fudge factors to default.
	 */
	for (i = 0; i < MAXUNITS; i++) {
		wwv_prop_delay[i].l_ui = 0;
		wwv_prop_delay[i].l_uf = DEFWWVPROP;
		pst_compute_delay(DEFWWVPROP, &wwv_prop_data[i]);
		wwvh_prop_delay[i].l_ui = 0;
		wwvh_prop_delay[i].l_uf = DEFWWVHPROP;
		pst_compute_delay(DEFWWVHPROP, &wwvh_prop_data[i]);
		stratumtouse[i] = 0;
		sloppyclock[i] = 0;
		freerun[i] = DEFMAXFREERUN;
	}
}


/*
 * pst_start - open the PST device and initialize data for processing
 */
static int
pst_start(unit, peer)
	u_int unit;
	struct peer *peer;
{
	register struct pstunit *pst;
	register int i;
	int fd232;
	char pstdev[20];

	/*
	 * Check configuration info
	 */
	if (unit >= MAXUNITS) {
		syslog(LOG_ERR, "pst_start: unit %d invalid", unit);
		return 0;
	}
	if (unitinuse[unit]) {
		syslog(LOG_ERR, "pst_start: unit %d in use", unit);
		return 0;
	}

	/*
	 * Open serial port
	 */
	(void) sprintf(pstdev, PSTDEV, unit);
	fd232 = open(pstdev, O_RDWR, 0777);
	if (fd232 == -1) {
		syslog(LOG_ERR, "pst_start: open of %s: %m", pstdev);
		return 0;
	}

#if defined(SYSV_TTYS)
	/*
	 * System V serial line parameters (termio interface)
	 *
	 * The PSTCLK support uses a 1-pps signal and level converter
	 * (gadget box). It requires the tty_clk line discipline and
	 * SysV v7 (?) or later.
	 */
    {	struct termio ttyb;
	if (ioctl(fd232, TCGETA, (char *)&ttyb) < 0) {
                syslog(LOG_ERR,
		    "pst_start: ioctl(%s, TCGETA): %m", pstdev);
                goto screwed;
        }
        ttyb.c_iflag = IGNBRK|IGNPAR|ICRNL;
        ttyb.c_oflag = 0;
        ttyb.c_cflag = SPEED232|CS8|CLOCAL|CREAD;
        ttyb.c_lflag = ICANON;
#if defined(PSTCLK)
	ttyb.c_line = CLKLDISC;
#endif /* PSTCLK */
	ttyb.c_cc[VERASE] = ttyb.c_cc[VKILL] = '\0';
        if (ioctl(fd232, TCSETA, (char *)&ttyb) < 0) {
                syslog(LOG_ERR,
		    "pst_start: ioctl(%s, TCSETA): %m", pstdev);
                goto screwed;
        }
    }
#endif /* SYSV_TTYS */
#if defined(STREAM)
	/*
	 * System V STREAM serial line parameters (termios interface)
	 *
	 * The PSTCLK support uses a 1-pps signal and level converter
	 * (gadget box). It requires the tty_clk streams module and
	 * SunOS 4.1.1 or later.
	 */
    {	struct termios ttyb;
	if (ioctl(fd232, TCGETS, (char *)&ttyb) < 0) {
                syslog(LOG_ERR,
		    "pst_start: ioctl(%s, TCGETS): %m", pstdev);
                goto screwed;
        }
        ttyb.c_iflag = IGNBRK|IGNPAR|ICRNL;
        ttyb.c_oflag = 0;
        ttyb.c_cflag = SPEED232|CS8|CLOCAL|CREAD;
        ttyb.c_lflag = ICANON;
	ttyb.c_cc[VERASE] = ttyb.c_cc[VKILL] = '\0';
        if (ioctl(fd232, TCSETS, (char *)&ttyb) < 0) {
                syslog(LOG_ERR,
		    "pst_start: ioctl(%s, TCSETS): %m", pstdev);
                goto screwed;
        }
#if defined(PSTCLK)
	if (ioctl(fd232, I_PUSH, "clk") < 0)
		syslog(LOG_ERR,
		    "pst_start: ioctl(%s, I_PUSH, clk): %m", pstdev);
	if (ioctl(fd232, CLK_SETSTR, "\n") < 0)
		syslog(LOG_ERR,
		    "pst_start: ioctl(%s, CLK_SETSTR): %m", pstdev);
#endif /* PSTCLK */
#if defined(PSTPPS)
	/*
	 * The following code is used only with the 1-pps signal and
	 * gadget box. It requires the ppsclock streams module and SunOS
	 * 4.1.1 or later.
	 */
	if (ioctl(fd232, I_PUSH, "ppsclock") < 0)
		syslog(LOG_ERR,
		    "pst_start: ioctl(%s, I_PUSH, ppsclock): %m", pstdev);
	else
		fdpps = fd232;
#endif /* PSTPPS */
    }
#endif /* STREAM */
#if defined(BSD_TTYS)
	/*
	 * 4.3bsd serial line parameters (sgttyb interface)
	 *
	 * The PSTCLK support uses a 1-pps signal and level converter
	 * (gadget box). It requires the tty_clk streams module and
	 * 4.3bsd or later.
	 */
    {	struct sgttyb ttyb;

	if (ioctl(fd232, TIOCGETP, (char *)&ttyb) < 0) {
		syslog(LOG_ERR,
		    "pst_start: ioctl(%s, TIOCGETP): %m", pstdev);
		goto screwed;
	}
	ttyb.sg_ispeed = ttyb.sg_ospeed = SPEED232;
#if defined(PSTCLK)
	ttyb.sg_erase = ttyb.sg_kill = '\r';
	ttyb.sg_flags = EVENP|ODDP|RAW|CRMOD;
#else
	ttyb.sg_erase = ttyb.sg_kill = '\0';
	ttyb.sg_flags = EVENP|ODDP|CRMOD|NOHANG;
#endif /* PSTCLK */
	if (ioctl(fd232, TIOCSETP, (char *)&ttyb) < 0) {
		syslog(LOG_ERR,
		    "pst_start: ioctl(%s, TIOCSETP): %m", pstdev);
		goto screwed;
	}
#if defined(PSTCLK)
	if (ioctl(fd232, TIOCSETD, CLKLDISC) < 0) {
		syslog(LOG_ERR,
		    "pst_start: ioctl(%s, TIOCSETD): %m",pstdev);
		goto screwed;
	}
#endif /* PSTCLK */
    }
#endif /* BSD_TTYS */

	/*
	 * Allocate unit structure
	 */
	if (pstunits[unit] != 0) {
		pst = pstunits[unit];	/* The one we want is okay */
	} else {
		for (i = 0; i < MAXUNITS; i++) {
			if (!unitinuse[i] && pstunits[i] != 0)
				break;
		}
		if (i < MAXUNITS) {
			/*
			 * Reclaim this one
			 */
			pst = pstunits[i];
			pstunits[i] = 0;
		} else {
			pst = (struct pstunit *)emalloc(sizeof(struct pstunit));
		}
	}
	bzero((char *)pst, sizeof(struct pstunit));
	pstunits[unit] = pst;

	/*
	 * Set up the structure
	 */
	pst->peer = peer;
	pst->unit = (u_char)unit;
	pst->state = STATE_IDLE;
	pst->flags |= PST_DOQV;
	pst->timestarted = current_time;
	(void) strcpy(pst->description, pstdefdesc);

	pst->psttimer.peer = (struct peer *)pst;
	pst->psttimer.event_handler = pst_timeout;

	pst->pstio.clock_recv = pst_receive;
	pst->pstio.srcclock = (caddr_t)pst;
	pst->pstio.datalen = 0;
	pst->pstio.fd = fd232;
	if (!io_addclock(&pst->pstio)) {
		goto screwed;
	}

	/*
	 * All done.  Initialize a few random peer variables, then
	 * start the timer and return success.
	 */
	peer->precision = PSTPRECISION;
	peer->rootdelay = 0;
	peer->rootdispersion = 0;
	peer->stratum = stratumtouse[unit];
	if (stratumtouse[unit] <= 1)
		bcopy(WWVREFID, (char *)&peer->refid, 4);
	else
		peer->refid = htonl(PSTHSREFID);
	pst->psttimer.event_time = current_time + PSTMINTIMEOUT;
	TIMER_ENQUEUE(timerqueue, &pst->psttimer);
	unitinuse[unit] = 1;
	return 1;

	/*
	 * Something broke; abandon ship.
	 */
screwed:
	(void) close(fd232);
	return (0);
}

/*
 * pst_shutdown - shut down a PST clock
 */
static void
pst_shutdown(unit)
	int unit;
{
	register struct pstunit *pst;

	if (unit >= MAXUNITS) {
		syslog(LOG_ERR, "pst_shutdown: unit %d invalid", unit);
		return;
	}
	if (!unitinuse[unit]) {
		syslog(LOG_ERR, "pst_shutdown: unit %d not in use", unit);
		return;
	}

	/*
	 * Tell the I/O module to turn us off, and dequeue timer
	 * if any.  We're history.
	 */
	pst = pstunits[unit];
	TIMER_DEQUEUE(&pst->psttimer);
	io_closeclock(&pst->pstio);
	unitinuse[unit] = 0;
}


/*
 * pst_write_error - complain about writes to the clock
 */
static void
pst_write_error(pst)
	struct pstunit *pst;
{
	/*
	 * This will fill syslog is something is really wrong.  Should
	 * throttle it back.
	 */
	syslog(LOG_ERR, "pst_write_error: unit %d: %m", pst->unit);
}


/*
 * pst_timeout - process a timeout event
 */
static void
pst_timeout(fakepeer)
	struct peer *fakepeer;
{
	register struct pstunit *pst;
	U_LONG poll;

	/*
	 * The timeout routine always initiates a chain of
	 * query-responses from the clock, by sending either
	 * a QV command (if we need to (re)set the propagation
	 * delays into the clock), a QM command or an SRY
	 * command (after a leap second).  The pst_receive()
	 * routine should complete the set of queries on its own
	 * LONG before the next time out is due, so if we see any
	 * state in here other than idle it means the clock hasn't
	 * responded.
	 */
	pst = (struct pstunit *)fakepeer;
	switch(pst->state) {
	case STATE_IDLE:
		poll = (U_LONG)psttab[pst->nextsample].nextinterval;
		break;				/* all is well */

	case STATE_QV:
		pst->flags |= PST_DOQV;		/* no response, do QV again */
		/*FALLSTHROUGH*/

	case STATE_QM:
	case STATE_QD:
	case STATE_QT:
		pst->noreply++;			/* mark the lack of response */
		poll = PSTMINTIMEOUT;		/* minimum time poll */
		break;

	default:
		syslog(LOG_ERR, "pst_timeout: unit %d invalid state %d",
		    pst->unit, pst->state);
		poll = PSTMINTIMEOUT;		/* minimum time poll */
		break;
	}

	if (pst->flags & PST_DORESET) {
		/*
		 * Do a reset.  At the next interrupt, start with
		 * a QV command to set in the delays.
		 */
		pst->flags &= ~PST_DORESET;
		pst->flags |= PST_DOQV;
		pst->state = STATE_IDLE;
		pst_send(pst, "\003SRY", 4);
	} else if (pst->flags & PST_DOQV) {
		pst->polls++;
		pst->flags &= ~PST_DOQV;
		pst->state = STATE_QV;
		pst_send(pst, "\003QV", 3);
	} else {
		pst->polls++;
		pst->state = STATE_QM;
		pst_send(pst, "\003QM", 3);
	}

	pst->psttimer.event_time += poll;
	TIMER_ENQUEUE(timerqueue, &pst->psttimer);
}


/*
 * pst_QV_process - decode the results of a QV poll and insert fudge
 *		    factors into the clock.
 */
static int
pst_QV_process(pst, rbufp)
	register struct pstunit *pst;
	struct recvbuf *rbufp;
{
	register char *cp;
	register char *bp;
	register int len;
	char *model;
	char *company;
	char buf[20];
	static char wwvdelay[6] = { 'S', 'C', '\0', 'S', 'E', '\0' };
	static char wwvhdelay[6] = { 'S', 'H', '\0', 'S', 'G', '\0' };

	/*
	 * The output of the QV command looks like:
	 *
	 * PSTI ITS V04.01.000\r
	 *
	 * or
	 *
	 * TRAC ITS V04.01.000\r
	 *
	 * or
	 *
	 * TRACONEX TS V05.02.001\r
	 *
	 * The minimum length of the string is about 16 characters.
	 * The maximum length is sort of unbounded, but we get suspicious
	 * if it is more than 34.
	 */
	len = rbufp->recv_length;
	if (len > PSTMAXQVLEN + 10)
		len = PSTMAXQVLEN + 10;
	
	bp = rbufp->recv_buffer;
	cp = pst->lastcode;
	while (len-- > 0) {
		*cp = (*bp++) & 0x7f;	/* strip parity */
		if (!isprint(*cp))
			break;
		cp++;
	}
	pst->lencode = (u_char)(cp - pst->lastcode);

	/*
	 * Okay, got all printable characters from the string
	 * copied.  We expect to have been terminated by the
	 * EOL character.  If not, forget it.  If the length
	 * is insane, forget it.
	 */

	if (*cp != PSTEOL
	    || pst->lencode < PSTMINQVLEN || pst->lencode > PSTMAXQVLEN) {
		pst->reason = QVREASON + 1;
		return 0;
	}

	/*
	 * Now, format check what we can.  Dump it at the least
	 * sign of trouble.
	 */
	cp = pst->lastcode;
	model = NULL;
	if (*cp++ != 'P' || *cp++ != 'S' || *cp++ != 'T'
	    || *cp++ != 'I' || *cp++ != ' ') {
		cp = pst->lastcode;
		if (*cp++ != 'T' || *cp++ != 'R' || *cp++ != 'A'
		    || *cp++ != 'C' || *cp++ != ' ') {
		   cp = pst->lastcode;
		   if (*cp++ != 'T' || *cp++ != 'R' || *cp++ != 'A'
			|| *cp++ != 'C' || *cp++ != 'O' || *cp++ != 'N'
			|| *cp++ != 'E' || *cp++ != 'X' || *cp != ' ') {
			   pst->reason = QVREASON + 2;
			   return 0;
		   }
		   company = "Traconex";
		   model = "1030";
		}
		company = "Traconex";
	} else {
		company = "Precision Standard Time";
	}

	if (*cp == 'M')
		model = "1010";
	else if (*cp == 'I')
		model = "1020";
	else if (model == NULL) {
		pst->reason = QVREASON + 3;
		return 0;
	}
	cp++;

	if (*cp++ != 'T' || *cp++ != 'S' || *cp++ != ' ') {
		pst->reason = QVREASON + 4;
		return 0;
	}
	if (*cp != 'X' && *cp != 'V') {
		pst->reason = QVREASON + 5;
		return 0;
	}
	
	/*
	 * Next is the version.  Copy it into the buffer.
	 */
	bp = buf;
	*bp++ = *cp++;
	while (isdigit(*cp) || *cp == '.')
		*bp++ = *cp++;
	*bp++ = '\0';

	/*
	 * Final bit of fluff is to set the description
	 */
	(void) sprintf(pst->description, PSTDESCRIPTION, company, model, buf);

	/*
	 * Now the serious stuff.  Since we are now sure that the
	 * clock is there, we can be fairly sure that the delay
	 * setting commands will take.  Send them.
	 */
	wwvdelay[2] = wwv_prop_data[pst->unit].msbchar;
	wwvdelay[5] = wwv_prop_data[pst->unit].lsbchar;
	pst_send(pst, wwvdelay, 6);

	/*
	 * Same thing for WWVH
	 */
	wwvhdelay[2] = wwvh_prop_data[pst->unit].msbchar;
	wwvhdelay[5] = wwvh_prop_data[pst->unit].lsbchar;
	pst_send(pst, wwvhdelay, 6);

	/*
	 * Should be okay.  Return positive response.
	 */
	return 1;
}


/*
 * pst_QM_process - process the output of a QM command
 */
static int
pst_QM_process(pst, rbufp)
	register struct pstunit *pst;
	struct recvbuf *rbufp;
{
	register char *cp;
	register char *bp;
	register int n;

	/*
	 * The output of the QM command looks like:
	 *
 	 * O6B532352823C00270322
	 *
	 * The minimum length of the string is 19 characters.
	 * The maximum length is sort of unbounded, but we get suspicious
	 * if it is more than 42.
	 */
	n = rbufp->recv_length;
	if (n > PSTMAXQMLEN + 10)
		n = PSTMAXQMLEN + 10;
	
	bp = rbufp->recv_buffer;
	cp = pst->lastcode;
	while (n-- > 0) {
		*cp = (*bp++) & 0x7f;	/* strip parity */
		if (!isprint(*cp))
			break;
		cp++;
	}
	pst->lencode = (u_char)(cp - pst->lastcode);

	/*
	 * Okay, got all printable characters from the string
	 * copied.  We expect to have been terminated by the
	 * EOL character.  If not, forget it.  If the length
	 * is insane, forget it.
	 */
	if (*cp != PSTEOL
	    || pst->lencode < PSTMINQMLEN || pst->lencode > PSTMAXQMLEN) {
		pst->reason = QMREASON + 1;
		return 0;
	}

	/*
	 * Ensure that the first PSTMINQMLEN characters are valid with
	 * respect to the way the clock encodes binary data.
	 */
	cp = pst->lastcode;
	n = pst->lencode;
	while (n-- > 0) {
		if (!ISVALIDPST(*cp)) {
			pst->reason = QMREASON + 2;
			return 0;
		}
		cp++;
	}

	/*
	 * Collect information we are interested in.
	 */
	cp = pst->lastcode;
	pst->timezone = PSTTOBIN(cp[3]);
	if (pst->timezone > 23) {
		pst->reason = QMREASON + 3;
		return 0;
	}

	pst->flags &=
	    ~(PST_LEAPYEAR|PST_SIGFAULT|PST_HARDERR|PST_NOTIME|PST_WWVH);
	n = PSTTOBIN(cp[4]);
	if (n > 15) {
		pst->reason = QMREASON + 4;
		return 0;
	}
	if (((n + 2) & 0x3) == 0)
		pst->flags |= PST_LEAPYEAR;

	n = PSTTOBIN(cp[9]);
	if (n > 15) {
		pst->reason = QMREASON + 5;
		return 0;
	}
	if (n & SIGFAULT)
		pst->flags |= PST_SIGFAULT;
	if (n & HARDFAULT)
		pst->flags |= PST_HARDERR;
	if (!(n & TIMEAVAILABLE))
		pst->flags |= PST_NOTIME;

	if (cp[12] == 'H') {
		pst->flags |= PST_WWVH;
	} else if (cp[12] == 'C') {
		pst->flags &= ~PST_WWVH;
	} else {
		pst->reason = QMREASON + 6;
		return 0;
	}

	if (wwv_prop_data[pst->unit].msbchar != cp[5] ||
	    wwv_prop_data[pst->unit].lsbchar != cp[6] ||
	    wwvh_prop_data[pst->unit].msbchar != cp[7] ||
	    wwvh_prop_data[pst->unit].lsbchar != cp[8])
		pst->flags |= PST_DOQV;

	bp = cp + 13;
	pst->timesincesync = 0;
	while (bp < (cp + 17)) {
		if (!isdigit(*bp)) {
			pst->reason = QMREASON + 6;
			return 0;
		}
		pst->timesincesync = MULBY10(pst->timesincesync)
		    + PSTTOBIN(*bp);
		bp++;
	}

	/*
	 * That's about all we can do.  Return success.
	 */
	return 1;
}


/*
 * pst_QD_process - process the output of a QD command
 */
static int
pst_QD_process(pst, rbufp)
	register struct pstunit *pst;
	struct recvbuf *rbufp;
{
	register char *cp;
	register char *bp;
	register int n;
	char *cpstart;
	int len;

	/*
	 * The output of the QM command looks like:
	 *
 	 * 88/05/17/138\r
	 *
	 * The minimum length of the string is 12 characters as is
	 * the maximum length.
	 */
	n = rbufp->recv_length;
	if (n > PSTMAXQDLEN + 10)
		n = PSTMAXQDLEN + 10;
	
	bp = rbufp->recv_buffer;
	cp = &pst->lastcode[pst->lencode];
	*cp++ = ' ';
	cpstart = cp;
	while (n-- > 0) {
		*cp = (*bp++) & 0x7f;	/* strip parity */
		if (!isprint(*cp))
			break;
		cp++;
	}
	len = (cp - cpstart);
	pst->lencode = (u_char)(cp - pst->lastcode);

	/*
	 * Okay, got all printable characters from the string
	 * copied.  We expect to have been terminated by the
	 * EOL character.  If not, forget it.  If the length
	 * is insane, forget it.
	 */
	if (*cp != PSTEOL ||
	    len < PSTMINQDLEN || len > PSTMAXQDLEN) {
		pst->reason = QDREASON + 1;
		return 0;
	}

	/*
	 * Ensure that the characters are formatted validly.  They
	 * are either digits or '/'s.
	 */
	cp = cpstart;
	if (!isdigit(cp[0]) || !isdigit(cp[1]) || cp[2] != '/' ||
	    !isdigit(cp[3]) || !isdigit(cp[4]) || cp[5] != '/' ||
	    !isdigit(cp[6]) || !isdigit(cp[7]) || cp[8] != '/' ||
	    !isdigit(cp[9]) || !isdigit(cp[10]) || !isdigit(cp[11])) {
		pst->reason = QDREASON + 2;
		return 0;
	}

	/*
	 * Decode into year, month, day and year day
	 */
	pst->year = MULBY10(PSTTOBIN(cp[0])) + PSTTOBIN(cp[1]);
	pst->month = MULBY10(PSTTOBIN(cp[3])) + PSTTOBIN(cp[4]);
	pst->monthday = MULBY10(PSTTOBIN(cp[6])) + PSTTOBIN(cp[7]);
	pst->yearday = MULBY10(PSTTOBIN(cp[9])) + PSTTOBIN(cp[10]);
	pst->yearday = MULBY10(pst->yearday) + PSTTOBIN(cp[11]);

	/*
	 * Format check these.
	 */
	if (pst->month > 12 || pst->monthday > 31 || pst->yearday > 366) {
		pst->reason = QDREASON + 3;
		return 0;
	}
	if (!(pst->flags & PST_LEAPYEAR) && pst->yearday > 365) {
		pst->reason = QDREASON + 4;
		return 0;
	}

	/*
	 * Done all we can.
	 */
	return 1;
}


/*
 * pst_QT_process - process the output of a QT command, return the times
 */
static int
pst_QT_process(pst, rbufp, tsclk, tsrec)
	register struct pstunit *pst;
	struct recvbuf *rbufp;
	l_fp *tsclk;
	l_fp *tsrec;
{
	register char *cp;
	register char *bp;
	register int n;
	char *cpstart;
	int len;
	int hour;
	int minute;
	int second;
	int msec;
	int tzoff;

	/*
	 * The output of the QT command looks like:
	 *
 	 * A09:57:50.263D
 	 * 
	 * The minimum length of the string is 14 characters as is
	 * the maximum length.
	 */
	n = rbufp->recv_length;
	if (n > PSTMAXQTLEN + 10)
		n = PSTMAXQTLEN + 10;
	
	bp = rbufp->recv_buffer;
	cp = &pst->lastcode[pst->lencode];
	*cp++ = ' ';
	cpstart = cp;
	while (n-- > 0) {
		*cp = (*bp++) & 0x7f;	/* strip parity */
		if (!isprint(*cp))
			break;
		cp++;
	}
	len = (cp - cpstart);
	pst->lencode = (u_char)(cp - pst->lastcode);

	/*
	 * Okay, got all printable characters from the string
	 * copied.  We expect to have been terminated by the
	 * EOL character.  If not, forget it.  If the length
	 * is insane, forget it.
	 */
	if (*cp != PSTEOL ||
	    len < PSTMINQTLEN || len > PSTMAXQTLEN) {
		pst->reason = QTREASON + 1;
		return 0;
	}
#ifdef PSTCLK
	/*
	 * Receive time stamp should be in buffer after the code.
	 * Make sure we have enough characters in there.
	 */
	if (&rbufp->recv_buffer[rbufp->recv_length] - bp < 8) {
		pst->reason = QTREASON + 2;
		return 0;
	}
	if (!buftvtots(bp, tsrec)) {
		pst->reason = QTREASON + 3;
		return 0;
	}
#else
	/*
	 * Use the timestamp collected with the input.
	 */
	*tsrec = rbufp->recv_time;
#endif

	/*
	 * Ensure that the characters are formatted validly.  Mostly
	 * digits, but the occasional `:' and `.'.
	 */
	cp = cpstart;
	if (!isdigit(cp[1]) || !isdigit(cp[2]) || cp[3] != ':' ||
	    !isdigit(cp[4]) || !isdigit(cp[5]) || cp[6] != ':' ||
	    !isdigit(cp[7]) || !isdigit(cp[8]) || cp[9] != '.' ||
	    !isdigit(cp[10]) || !isdigit(cp[11]) || !isdigit(cp[12])) {
		pst->reason = QTREASON + 4;
		return 0;
	}

	/*
	 * Extract the hour, minute, second and millisecond
	 */
	hour = MULBY10(PSTTOBIN(cp[1])) + PSTTOBIN(cp[2]);
	minute = MULBY10(PSTTOBIN(cp[4])) + PSTTOBIN(cp[5]);
	second = MULBY10(PSTTOBIN(cp[7])) + PSTTOBIN(cp[8]);
	msec = MULBY10(PSTTOBIN(cp[10])) + PSTTOBIN(cp[11]);
	msec = MULBY10(msec) + PSTTOBIN(cp[12]);

	if (minute > 59 || second > 59) {
		pst->reason = QTREASON + 5;
		return 0;
	}

	/*
	 * Trouble here.  Adjust the hours for AM/PM, if this is
	 * on, and for daylight saving time.
	 */
	if (*cp == 'A') {
		if (hour > 12 || hour == 0) {
			pst->reason = QTREASON + 5;
			return 0;
		}
		if (hour == 12)
			hour = 0;
	} else if (*cp == 'P') {
		if (hour > 12 || hour == 0)
			return 0;
		if (hour < 12)
			hour += 12;
	} else if (*cp != ' ') {
		pst->reason = QTREASON + 6;
		return 0;
	}

	if (cp[13] == 'D')
		tzoff = -1;
	else if (cp[13] == ' ')
		tzoff = 0;
	else {
		pst->reason = QTREASON + 7;
		return 0;
	}

	/*
	 * Adjust for the timezone.  The PST manual is screwy here.
	 * it says the timezone is an integer in the range 0 to 23,
	 * but this doesn't allow us to tell the difference between
	 * +12 and -12.  Assume the 12 hour timezone is west of
	 * GMT.
	 */
	if (pst->timezone <= 12)
		tzoff += pst->timezone;
	else
		tzoff -= (24 - pst->timezone);


	/*
	 * Record for posterity
	 */
	pst->hour = (u_char)hour;
	pst->minute = (u_char)minute;
	pst->second = (u_char)second;
	pst->millisecond = (u_short)msec;
	pst->tzoffset = (s_char)tzoff;

	/*
	 * All that to get the day-hour-minute-second.  Turn this
	 * into the seconds part of a time stamp.  Also use the
	 * milliseconds part directly as the fractional part.
	 */
	MSUTOTSF(msec, tsclk->l_uf);
	if (!clocktime((int)pst->yearday, hour, minute, second, tzoff,
	    tsrec->l_ui, &pst->yearstart, &tsclk->l_ui)) {
		pst->reason = QTREASON + 8;
		return 0;
	}

	/*
	 * Add in the fudge
	 */
	if (pst->flags & PST_WWVH)
		L_ADDUF(tsclk, wwvh_prop_data[pst->unit].remainder);
	else
		L_ADDUF(tsclk, wwv_prop_data[pst->unit].remainder);

	/*
	 * Glad that's over with
	 */
	return 1;
}


/*
 * pst_do_event - update our status and report any changes
 */
static void
pst_do_event(pst, evnt_code)
	register struct pstunit *pst;
	int evnt_code;
{
	if (pst->status != (u_char)evnt_code) {
		pst->status = (u_char)evnt_code;
		if (evnt_code != CEVNT_NOMINAL)
			pst->lastevent = (u_char)evnt_code;
		/*
		 * Should trap this, but the trap code isn't up to
		 * it yet.
		 */
	}
}



/*
 * pst_process - process the data collected to produce an offset estimate
 */
static void
pst_process(pst)
	register struct pstunit *pst;
{
	register int i;
	register int n;
	register U_LONG tmp_ui;
	register U_LONG tmp_uf;
	register U_LONG date_ui;
	register U_LONG date_uf;
	u_fp dispersion;
	l_fp off[NPSTSAMPS];

	/*
	 * Compute offsets from the raw data.  Sort them into
	 * ascending order.
	 */
	for (i = 0; i < NPSTSAMPS; i++) {
		tmp_ui = pst->reftimes[i].l_ui;
		tmp_uf = pst->reftimes[i].l_uf;
		M_SUB(tmp_ui, tmp_uf, pst->rectimes[i].l_ui,
		    pst->rectimes[i].l_uf);
		for (n = i; n > 0; n--) {
			if (M_ISGEQ(tmp_ui, tmp_uf, off[n-1].l_ui,
			    off[n-1].l_uf))
				break;
			off[n] = off[n-1];
		}
		off[n].l_ui = tmp_ui;
		off[n].l_uf = tmp_uf;
	}

	/*
	 * Reject the furthest from the median until 8 samples left
	 */
	i = 0;
	n = NPSTSAMPS;
	while ((n - i) > 8) {
		tmp_ui = off[n-1].l_ui;
		tmp_uf = off[n-1].l_uf;
		date_ui = off[(n+i)/2].l_ui;
		date_uf = off[(n+i)/2].l_uf;
		M_SUB(tmp_ui, tmp_uf, date_ui, date_uf);
		M_SUB(date_ui, date_uf, off[i].l_ui, off[i].l_uf);
		if (M_ISHIS(date_ui, date_uf, tmp_ui, tmp_uf)) {
			/*
			 * reject low end
			 */
			i++;
		} else {
			/*
			 * reject high end
			 */
			n--;
		}
	}

	/*
	 * Compute the dispersion based on the difference between the
	 * extremes of the remaining offsets.
	 */
	tmp_ui = off[n-1].l_ui;
	tmp_uf = off[n-1].l_uf;
	M_SUB(tmp_ui, tmp_uf, off[i].l_ui, off[i].l_uf);
	dispersion = MFPTOFP(tmp_ui, tmp_uf);

	/*
	 * Now compute the offset estimate.  If the sloppy clock
	 * flag is set, average the remainder, otherwise pick the
	 * median.
	 */
	if (sloppyclock[pst->unit]) {
		tmp_ui = tmp_uf = 0;
		while (i < n) {
			M_ADD(tmp_ui, tmp_uf, off[i].l_ui, off[i].l_uf);
			i++;
		}
		M_RSHIFT(tmp_ui, tmp_uf);
		M_RSHIFT(tmp_ui, tmp_uf);
		M_RSHIFT(tmp_ui, tmp_uf);
		i = 0;
		off[0].l_ui = tmp_ui;
		off[0].l_uf = tmp_uf;
	} else {
		i = (n+i)/2;
	}

	/*
	 * Add the default PST QT delay into this.
	 */
	L_ADDUF(&off[i], PSTQTFUDGE);

	/*
	 * Set the reference ID to the appropriate station
	 */
	if (stratumtouse[pst->unit] <= 1) {
		if (pst->station >= 0)
			bcopy(WWVREFID, (char *)&pst->peer->refid, 4);
		else
			bcopy(WWVHREFID, (char *)&pst->peer->refid, 4);
	}

	/*
	 * Give the data to the reference clock support code
	 */
	refclock_receive(pst->peer, &off[i], 0, dispersion, &pst->reftimes[NPSTSAMPS-1],
	    &pst->rectimes[NPSTSAMPS-1], pst->leap);

	/*
	 * If the don't-sync flag isn't on, we're nominal.
	 */
	if (pst->leap == 0)
		pst_event(pst, CEVNT_NOMINAL);
	pst_reset(pst);
}



/*
 * pst_receive - receive data from a PST clock, call the appropriate
 *		 routine to process it, and advance the state.
 */
static void
pst_receive(rbufp)
	struct recvbuf *rbufp;
{
	register struct pstunit *pst;
	register U_LONG tmp;

	pst = (struct pstunit *)rbufp->recv_srcclock;

	/*
	 * Process based on the current state.
	 */
	switch(pst->state) {
	case STATE_IDLE:
		return;			/* Ignore the input */

	case STATE_QV:
		if (!pst_QV_process(pst, rbufp)) {
			/*
			 * Set the state to idle, but request another
			 * QV poll.
			 */
			pst->badformat++;
			pst_event(pst, CEVNT_BADREPLY);
			pst->state = STATE_IDLE;
			pst->flags |= PST_DOQV;
		} else {
			/*
			 * This went okay.  Advance the state to
			 * QM and send the request.
			 */
			pst->state = STATE_QM;
			pst_send(pst, "QM", 2);
		}
		return;

	case STATE_QM:
		if (!pst_QM_process(pst, rbufp)) {
			/*
			 * Idle us and note the error
			 */
			pst->badformat++;
			pst_event(pst, CEVNT_BADREPLY);
			pst->state = STATE_IDLE;
			return;
		}
		if (pst->flags & PST_NOTIME) {
			/*
			 * Here we aren't getting any time because the
			 * clock is still searching.  Don't bother
			 * looking for anything.  Remove any leap
			 * second hold, however, since this should
			 * ensure the clock is sensible.
			 */
			pst_event(pst, CEVNT_FAULT);
			pst->state = STATE_IDLE;
			if (pst->nextsample > 0)
				pst_reset(pst);		/* Make sure rate low */
			return;
		}

		/*
		 * Next is QD.  Do it.
		 */
		pst->state = STATE_QD;
		pst_send(pst, "QD", 2);
		return;

	case STATE_QD:
		if (!pst_QD_process(pst, rbufp)) {
			/*
			 * Idle us and note the error
			 */
			pst->badformat++;
			pst_event(pst, CEVNT_BADDATE);
			pst->state = STATE_IDLE;
		} else {
			/*
			 * Last step is QT.
			 */
			pst->state = STATE_QT;
			pst_send(pst, "QT", 2);
		}
		return;

	case STATE_QT:
		pst->state = STATE_IDLE;
		if (!pst_QT_process(pst, rbufp, &pst->lastref, &pst->lastrec)) {
			/*
			 * Note the error
			 */
			pst->baddata++;
			pst_event(pst, CEVNT_BADTIME);
			return;
		}
		break;

	default:
		syslog(LOG_ERR,
		    "pst_receive: unit %d invalid state %d",
		    pst->unit, pst->state);
		return;
	}


	/*
	 * You may not have noticed this, but the only way we end up
	 * out here is if we've completed polling and have a couple of
	 * valid time stamps.  First see if we should reset the
	 * structure.
	 */
	if (pst->nextsample > 0) {
		tmp = pst->lastrec.l_ui - pst->rectimes[0].l_ui;
		if (tmp > (U_LONG)psttab[pst->nextsample].tooold)
			pst_reset(pst);
	}

	pst->rectimes[pst->nextsample] = pst->lastrec;
	pst->reftimes[pst->nextsample] = pst->lastref;
	pst->nextsample++;
	if (pst->flags & PST_WWVH)
		pst->station--;
	else
		pst->station++;

	if (pst->flags & (PST_SIGFAULT|PST_HARDERR)) {
		pst_event(pst, CEVNT_FAULT);
		pst->leap = LEAP_NOTINSYNC;
	} else if (pst->timesincesync > freerun[pst->unit]) {
		pst_event(pst, CEVNT_PROP);
		pst->leap = LEAP_NOTINSYNC;
	}

	if (pst->nextsample >= NPSTSAMPS)
		pst_process(pst);
}


/*
 * pst_compute_delay - compute appropriate things to tell clock about delays
 */
static void
pst_compute_delay(prop_delay, prop_data)
	U_LONG prop_delay;
	struct pst_propagate *prop_data;
{
	register int code;
	register U_LONG tsf;

	/*
	 * Convert (truncate) the delay to milliseconds.  Save the
	 * characters needed to send this to the clock and compute
	 * the remainder to be added in later.
	 */
	code = tsftomsu(prop_delay, 0);
	MSUTOTSF(code, tsf);
	prop_data->remainder = prop_delay - tsf;
	if (prop_data->remainder & 0x80000000)
		prop_data->remainder = 0;
	prop_data->msbchar = BINTOPST((code >> 2) & 0x1f);
	prop_data->lsbchar = BINTOPST(code & 0x3);
}


/*
 * pst_control - set fudge factors, return statistics
 */
static void
pst_control(unit, in, out)
	u_int unit;
	struct refclockstat *in;
	struct refclockstat *out;
{
	register struct pstunit *pst;

	if (unit >= MAXUNITS) {
		syslog(LOG_ERR, "pst_control: unit %d invalid", unit);
		return;
	}

	if (in != 0) {
		int doqv = 0;

		if (in->haveflags & CLK_HAVETIME1)
			if (in->fudgetime1.l_ui == 0
			    && in->fudgetime1.l_uf <= PSTMAXPROP) {
				wwv_prop_delay[unit] = in->fudgetime1;
				doqv = 1;
				pst_compute_delay(wwv_prop_delay[unit].l_uf,
				    &wwv_prop_data[unit]);
			}
		if (in->haveflags & CLK_HAVETIME2)
			if (in->fudgetime2.l_ui == 0
			    && in->fudgetime2.l_uf <= PSTMAXPROP) {
				wwvh_prop_delay[unit] = in->fudgetime2;
				doqv = 1;
				pst_compute_delay(wwvh_prop_delay[unit].l_uf,
				    &wwvh_prop_data[unit]);
			}
		if (in->haveflags & CLK_HAVEVAL1) {
			stratumtouse[unit] = (u_char)(in->fudgeval1 & 0xf);
		}
		if (in->haveflags & CLK_HAVEVAL2) {
			if (in->fudgeval2 > 0 && in->fudgeval2 < 9990)
				freerun[unit] = (u_short)in->fudgeval2;
		}
		if (in->haveflags & CLK_HAVEFLAG1) {
			sloppyclock[unit] = in->flags & CLK_FLAG1;
		}
		if (unitinuse[unit]) {
			/*
			 * Should actually reselect clock, but
			 * will wait for the next timecode
			 */
			if (in->haveflags & CLK_HAVEVAL1) {
				pstunits[unit]->peer->stratum
				    = stratumtouse[unit];
				if (stratumtouse[unit] > 1)
					pstunits[unit]->peer->refid
					    = htonl(PSTHSREFID);
			}

			if ((in->haveflags & CLK_HAVEFLAG3) &&
			    (in->flags & CLK_FLAG3)) {
				pstunits[unit]->flags |= PST_DORESET;
			} else if (doqv || ((in->haveflags & CLK_HAVEFLAG2) &&
			    (in->flags & CLK_FLAG2))) {
				pstunits[unit]->flags |= PST_DOQV;
			}
		}
	}

	if (out != 0) {
		out->type = REFCLK_WWV_PST;
		out->flags = 0;
		out->haveflags
		    = CLK_HAVETIME1|CLK_HAVETIME2|CLK_HAVEVAL1|
		      CLK_HAVEVAL2|CLK_HAVEFLAG1;
		out->fudgetime1 = wwv_prop_delay[unit];
		out->fudgetime2 = wwvh_prop_delay[unit];
		out->fudgeval1 = (LONG)stratumtouse[unit];
		out->fudgeval2 = (LONG)freerun[unit];
		out->flags = sloppyclock[unit];
		if (unitinuse[unit]) {
			pst = pstunits[unit];
			out->clockdesc = pst->description;
			out->lencode = pst->lencode;
			out->lastcode = pst->lastcode;
			out->timereset = current_time - pst->timestarted;
			out->polls = pst->polls;
			out->noresponse = pst->noreply;
			out->badformat = pst->badformat;
			out->baddata = pst->baddata;
			out->lastevent = pst->lastevent;
			out->currentstatus = pst->status;
		} else {
			out->clockdesc = pstdefdesc;
			out->lencode = 0;
			out->lastcode = "";
			out->polls = out->noresponse = 0;
			out->badformat = out->baddata = 0;
			out->timereset = 0;
			out->currentstatus = out->lastevent = CEVNT_NOMINAL;
		}
	}
}


/*
 * pst_buginfo - return clock dependent debugging info
 */
static void
pst_buginfo(unit, bug)
	int unit;
	register struct refclockbug *bug;
{
	register struct pstunit *pst;
	register int i;

	bug->nvalues = bug->ntimes = 0;

	if (unit >= MAXUNITS) {
		syslog(LOG_ERR, "pst_buginfo: unit %d invalid", unit);
		return;
	}

	if (!unitinuse[unit])
		return;
	pst = pstunits[unit];

	bug->nvalues = 14;
	bug->svalues = (1<<10);
	bug->values[0] = (U_LONG)pst->nextsample;
	bug->values[1] = (U_LONG)pst->state;
	bug->values[2] = (U_LONG)pst->reason;
	bug->values[3] = (U_LONG)pst->flags;
	bug->values[4] = (U_LONG)pst->yearday;
	bug->values[5] = (U_LONG)pst->hour;
	bug->values[6] = (U_LONG)pst->minute;
	bug->values[7] = (U_LONG)pst->second;
	bug->values[8] = (U_LONG)pst->millisecond;
	bug->values[9] = (U_LONG)pst->timezone;
	bug->values[10] = (U_LONG)((LONG)pst->tzoffset);
	bug->values[11] = (U_LONG)pst->timesincesync;
	bug->values[12] = pst->yearstart;
	bug->ntimes = ((NPSTSAMPS*2)+2) > NCLKBUGTIMES ? NCLKBUGTIMES :
	    ((NPSTSAMPS*2)+2);
	bug->stimes = 0;
	for (i = 0; i < (bug->ntimes-2)/2; i++) {
		bug->times[2*i] = pst->rectimes[i];
		bug->times[(2*i) + 1] = pst->reftimes[i];
	}
	bug->times[bug->ntimes - 2] = pst->lastrec;
	bug->times[bug->ntimes - 1] = pst->lastref;
}
#endif
