refclock_acts.c   [plain text]


/*
 * refclock_acts - clock driver for the NIST/USNO/PTB/NPL Computer Time
 *	Services
 */
#ifdef HAVE_CONFIG_H
#include <config.h>
#endif

#if defined(REFCLOCK) && defined(CLOCK_ACTS)

#include "ntpd.h"
#include "ntp_io.h"
#include "ntp_unixtime.h"
#include "ntp_refclock.h"
#include "ntp_stdlib.h"
#include "ntp_control.h"

#include <stdio.h>
#include <ctype.h>
#ifdef HAVE_SYS_IOCTL_H
# include <sys/ioctl.h>
#endif /* HAVE_SYS_IOCTL_H */

#ifdef SYS_WINNT
#undef write	/* ports/winnt/include/config.h: #define write _write */
extern int async_write(int, const void *, unsigned int);
#define write(fd, data, octets)	async_write(fd, data, octets)
#endif

/*
 * This driver supports the US (NIST, USNO) and European (PTB, NPL,
 * etc.) modem time services, as well as Spectracom GPS and WWVB
 * receivers connected via a modem. The driver periodically dials a
 * number from a telephone list, receives the timecode data and
 * calculates the local clock correction. It is designed primarily for
 * use as backup when neither a radio clock nor connectivity to Internet
 * time servers is available.
 *
 * This driver requires a modem with a Hayes-compatible command set and
 * control over the modem data terminal ready (DTR) control line. The
 * modem setup string is hard-coded in the driver and may require
 * changes for nonstandard modems or special circumstances.
 *
 * When enabled, the calling program dials the first number in the
 * phones file. If that call fails, it dials the second number and
 * so on. The phone number is specified by the Hayes ATDT prefix
 * followed by the number itself, including the long-distance prefix
 * and delay code, if necessary. The calling program is enabled
 * when (a) fudge flag1 is set by ntpdc, (b) at each poll interval
 * when no other synchronization sources are present, and (c) at each
 * poll interval whether or not other synchronization sources are 
 * present. The calling program disconnects if (a) the called party
 * is busy or does not answer, (b) the called party disconnects
 * before a sufficient nuimber of timecodes have been received. 
 *
 * The driver is transparent to each of the modem time services and
 * Spectracom radios. It selects the parsing algorithm depending on the
 * message length. There is some hazard should the message be corrupted.
 * However, the data format is checked carefully and only if all checks
 * succeed is the message accepted. Corrupted lines are discarded
 * without complaint.
 *
 * Fudge controls
 *
 * flag1	force a call in manual mode
 * flag2	enable port locking (not verified)
 * flag3	not used
 * flag4	not used
 *
 * time1	offset adjustment (s)
 *
 * Ordinarily, the serial port is connected to a modem and the phones
 * list is defined. If no phones list is defined, the port can be 
 * connected directly to a device or another computer. In this case the
 * driver will send a single character 'T' at each poll event. If
 * fudge flag2 is enabled, port locking allows the modem to be shared
 * when not in use by this driver.
 */
/*
 * National Institute of Science and Technology (NIST)
 *
 * Phone: (303) 494-4774 (Boulder, CO); (808) 335-4721 (Hawaii)
 *
 * Data Format
 *
 * National Institute of Standards and Technology
 * Telephone Time Service, Generator 3B
 * Enter question mark "?" for HELP
 *                         D  L D
 *  MJD  YR MO DA H  M  S  ST S UT1 msADV        <OTM>
 * 47999 90-04-18 21:39:15 50 0 +.1 045.0 UTC(NIST) *<CR><LF>
 * ...
 *
 * MJD, DST, DUT1 and UTC are not used by this driver. The "*" or "#" is
 * the on-time markers echoed by the driver and used by NIST to measure
 * and correct for the propagation delay. Note: the ACTS timecode has
 * recently been changed to eliminate the * on-time indicator. The
 * reason for this and the long term implications are not clear.
 *
 * US Naval Observatory (USNO)
 *
 * Phone: (202) 762-1594 (Washington, DC); (719) 567-6742 (Boulder, CO)
 *
 * Data Format (two lines, repeating at one-second intervals)
 *
 * jjjjj nnn hhmmss UTC<CR><LF>
 * *<CR><LF>
 *
 * jjjjj	modified Julian day number (not used)
 * nnn		day of year
 * hhmmss	second of day
 * *		on-time marker for previous timecode
 * ...
 *
 * USNO does not correct for the propagation delay. A fudge time1 of
 * about .06 s is advisable.
 *
 * European Services (PTB, NPL, etc.)
 *
 * PTB: +49 531 512038 (Germany)
 * NPL: 0906 851 6333 (UK only)
 *
 * Data format (see the documentation for phone numbers and formats.)
 *
 * 1995-01-23 20:58:51 MEZ  10402303260219950123195849740+40000500<CR><LF>
 *
 * Spectracom GPS and WWVB Receivers
 *
 * If a modem is connected to a Spectracom receiver, this driver will
 * call it up and retrieve the time in one of two formats. As this
 * driver does not send anything, the radio will have to either be
 * configured in continuous mode or be polled by another local driver.
 */
/*
 * Interface definitions
 */
#define	DEVICE		"/dev/acts%d" /* device name and unit */
#define	SPEED232	B19200	/* uart speed (19200 bps) */
#define	PRECISION	(-10)	/* precision assumed (about 1 ms) */
#define LOCKFILE	"/var/spool/lock/LCK..cua%d"
#define DESCRIPTION	"Automated Computer Time Service" /* WRU */
#define REFID		"NONE"	/* default reference ID */
#define MSGCNT		20	/* max message count */
#define	MAXPHONE	10	/* max number of phone numbers */

/*
 * Calling program modes (mode)
 */
#define MODE_BACKUP	0	/* backup mode */
#define MODE_AUTO	1	/* automatic mode */
#define MODE_MANUAL	2	/* manual mode */

/*
 * Service identifiers (message length)
 */
#define REFACTS		"NIST"	/* NIST reference ID */
#define LENACTS		50	/* NIST format A */
#define REFUSNO		"USNO"	/* USNO reference ID */
#define LENUSNO		20	/* USNO */
#define REFPTB		"PTB\0"	/* PTB/NPL reference ID */
#define LENPTB		78	/* PTB/NPL format */
#define REFWWVB		"WWVB"	/* WWVB reference ID */
#define	LENWWVB0	22	/* WWVB format 0 */
#define	LENWWVB2	24	/* WWVB format 2 */
#define LF		0x0a	/* ASCII LF */

/*
 * Modem setup strings. These may have to be changed for
 * some modems.
 *
 * AT	command prefix
 * B1	US answer tone
 * &C0	disable carrier detect
 * &D2	hang up and return to command mode on DTR transition
 * E0	modem command echo disabled
 * L1	set modem speaker volume to low level
 * M1	speaker enabled until carrier detect
 * Q0	return result codes
 * V1	return result codes as English words
 * Y1	enable long-space disconnect
 */
const char def_modem_setup[] = "ATB1&C0&D2E0L1M1Q0V1Y1";
const char *modem_setup = def_modem_setup; 

/*
 * Timeouts (all in seconds)
 */
#define SETUP		3	/* setup timeout */
#define	REDIAL		30	/* redial timeout */
#define ANSWER		60	/* answer timeout */
#define TIMECODE	60	/* message timeout */
#define	MAXCODE		20	/* max timecodes */

/*
 * State machine codes
 */
typedef enum {
	S_IDLE,			/* wait for poll */
	S_SETUP,		/* send modem setup */
	S_CONNECT,		/* wait for answer */
	S_MSG			/* wait for timecode */
} teModemState;

/*
 * Unit control structure
 */
struct actsunit {
	int	unit;		/* unit number */
	int	state;		/* the first one was Delaware */
	int	timer;		/* timeout counter */
	int	retry;		/* retry index */
	int	msgcnt;		/* count of messages received */
	l_fp	tstamp;		/* on-time timestamp */
	char	*bufptr;	/* next incoming char stored here */
	char	buf[BMAX];	/* bufptr roams within buf[] */
};

/*
 * Function prototypes
 */
static	int	acts_start	(int, struct peer *);
static	void	acts_shutdown	(int, struct peer *);
static	void	acts_receive	(struct recvbuf *);
static	void	acts_message	(struct peer *, const char *);
static	void	acts_timecode	(struct peer *, const char *);
static	void	acts_poll	(int, struct peer *);
static	void	acts_timeout	(struct peer *, teModemState);
static	void	acts_timer	(int, struct peer *);
static	void	acts_close	(struct peer *);

/*
 * Transfer vector (conditional structure name)
 */
struct refclock refclock_acts = {
	acts_start,		/* start up driver */
	acts_shutdown,		/* shut down driver */
	acts_poll,		/* transmit poll message */
	noentry,		/* not used */
	noentry,		/* not used */
	noentry,		/* not used */
	acts_timer		/* housekeeping timer */
};

/*
 * Initialize data for processing
 */
static int
acts_start(
	int	unit,
	struct peer *peer
	)
{
	struct actsunit *up;
	struct refclockproc *pp;
	const char *setup;

	/*
	 * Allocate and initialize unit structure
	 */
	up = emalloc_zero(sizeof(struct actsunit));
	up->unit = unit;
	pp = peer->procptr;
	pp->unitptr = up;
	pp->io.clock_recv = acts_receive;
	pp->io.srcclock = peer;
	pp->io.datalen = 0;
	pp->io.fd = -1;

	/*
	 * Initialize miscellaneous variables
	 */
	peer->precision = PRECISION;
	pp->clockdesc = DESCRIPTION;
	memcpy(&pp->refid, REFID, 4);
	peer->sstclktype = CTL_SST_TS_TELEPHONE;
	up->bufptr = up->buf;
	if (def_modem_setup == modem_setup) {
		setup = get_ext_sys_var("modemsetup");
		if (setup != NULL)
			modem_setup = estrdup(setup);
	}

	return (1);
}


/*
 * acts_shutdown - shut down the clock
 */
static void
acts_shutdown(
	int	unit,
	struct peer *peer
	)
{
	struct actsunit *up;
	struct refclockproc *pp;

	/*
	 * Warning: do this only when a call is not in progress.
	 */
	pp = peer->procptr;
	up = pp->unitptr;
	acts_close(peer);
	free(up);
}


/*
 * acts_receive - receive data from the serial interface
 */
static void
acts_receive(
	struct recvbuf *rbufp
	)
{
	struct actsunit *up;
	struct refclockproc *pp;
	struct peer *peer;
	char	tbuf[sizeof(up->buf)];
	char *	tptr;
	int	octets;

	/*
	 * Initialize pointers and read the timecode and timestamp. Note
	 * we are in raw mode and victim of whatever the terminal
	 * interface kicks up; so, we have to reassemble messages from
	 * arbitrary fragments. Capture the timecode at the beginning of
	 * the message and at the '*' and '#' on-time characters.
	 */
	peer = rbufp->recv_peer;
	pp = peer->procptr;
	up = pp->unitptr;
	octets = sizeof(up->buf) - (up->bufptr - up->buf);
	refclock_gtraw(rbufp, tbuf, octets, &pp->lastrec);
	for (tptr = tbuf; *tptr != '\0'; tptr++) {
		if (*tptr == LF) {
			if (up->bufptr == up->buf) {
				up->tstamp = pp->lastrec;
				continue;
			} else {
				*up->bufptr = '\0';
				up->bufptr = up->buf;
				acts_message(peer, up->buf);
			}
		} else if (!iscntrl((unsigned char)*tptr)) {
			*up->bufptr++ = *tptr;
			if (*tptr == '*' || *tptr == '#') {
				up->tstamp = pp->lastrec;
				if (write(pp->io.fd, tptr, 1) < 0)
					msyslog(LOG_ERR, "acts: write echo fails %m");
			}
		}
	}
}


/*
 * acts_message - process message
 */
void
acts_message(
	struct peer *peer,
	const char *msg
	)
{
	struct actsunit *up;
	struct refclockproc *pp;
	char	tbuf[BMAX];
	int		dtr = TIOCM_DTR;

	DPRINTF(1, ("acts: %d %s\n", (int)strlen(msg), msg));

	/*
	 * What to do depends on the state and the first token in the
	 * message.
	 */
	pp = peer->procptr;
	up = pp->unitptr;

	/*
	 * Extract the first token in the line.
	 */
	strlcpy(tbuf, msg, sizeof(tbuf));
	strtok(tbuf, " ");
	switch (up->state) {

	/*
	 * We are waiting for the OK response to the modem setup
	 * command. When this happens, dial the number followed.
	 * If anything other than OK is received, just ignore it
	 * and wait for timeoue.
	 */
	case S_SETUP:
		if (strcmp(tbuf, "OK") != 0) {
			/*
			 * We disable echo with MODEM_SETUP's E0 but
			 * if the modem was previously E1, we will
			 * see MODEM_SETUP echoed before the OK/ERROR.
			 * Ignore it.
			 */
			if (!strcmp(tbuf, modem_setup))
				return;
			break;
		}

		mprintf_event(PEVNT_CLOCK, peer, "DIAL #%d %s",
			      up->retry, sys_phone[up->retry]);
		if (ioctl(pp->io.fd, TIOCMBIS, &dtr) < 0)
			msyslog(LOG_ERR, "acts: ioctl(TIOCMBIS) failed: %m");
		if (write(pp->io.fd, sys_phone[up->retry],
		    strlen(sys_phone[up->retry])) < 0)
			msyslog(LOG_ERR, "acts: write DIAL fails %m");
		write(pp->io.fd, "\r", 1);
		up->retry++;
		up->state = S_CONNECT;
		up->timer = ANSWER;
		return;

	/*
	 * We are waiting for the CONNECT response to the dial
	 * command. When this happens, listen for timecodes. If
	 * somthing other than CONNECT is received, like BUSY
	 * or NO CARRIER, abort the call.
	 */
	case S_CONNECT:
		if (strcmp(tbuf, "CONNECT") != 0)
			break;

		report_event(PEVNT_CLOCK, peer, msg);
		up->state = S_MSG;
		up->timer = TIMECODE;
		return;

	/*
	 * We are waiting for a timecode response. Pass it to
	 * the parser. If NO CARRIER is received, save the
	 * messages and abort the call.
	 */
	case S_MSG:
		if (strcmp(tbuf, "NO") == 0)
			report_event(PEVNT_CLOCK, peer, msg);
		if (up->msgcnt < MAXCODE)
			acts_timecode(peer, msg);
		else
			acts_timeout(peer, S_MSG);
		return;
	}

	/*
	 * Other response. Tell us about it.
	 */
	report_event(PEVNT_CLOCK, peer, msg);
	acts_close(peer);
}


/*
 * acts_timeout - called on timeout
 */
static void
acts_timeout(
	struct peer *peer,
	teModemState	dstate
	)
{
	struct actsunit *up;
	struct refclockproc *pp;
	int	fd;
	int	rc;
	char	device[20];
	char	lockfile[128], pidbuf[8];

	/*
	 * The state machine is driven by messages from the modem,
	 * when first started and at timeout.
	 */
	pp = peer->procptr;
	up = pp->unitptr;
	switch (dstate) {

	/*
	 * System poll event. Lock the modem port, open the device
	 * and send the setup command.
	 */
	case S_IDLE:
		if (-1 != pp->io.fd)
			return;		/* port is already open */

		/*
		 * Lock the modem port. If busy, retry later. Note: if
		 * something fails between here and the close, the lock
		 * file may not be removed.
		 */
		if (pp->sloppyclockflag & CLK_FLAG2) {
			snprintf(lockfile, sizeof(lockfile), LOCKFILE,
			    up->unit);
			fd = open(lockfile, O_WRONLY | O_CREAT | O_EXCL,
			    0644);
			if (fd < 0) {
				report_event(PEVNT_CLOCK, peer, "acts: port busy");
				return;
			}
			snprintf(pidbuf, sizeof(pidbuf), "%d\n",
			    (u_int)getpid());
			if (write(fd, pidbuf, strlen(pidbuf)) < 0)
				msyslog(LOG_ERR, "acts: write lock fails %m");
			close(fd);
		}

		/*
		 * Open the device in raw mode and link the I/O.
		 */
		snprintf(device, sizeof(device), DEVICE,
		    up->unit);
		fd = refclock_open(device, SPEED232, LDISC_ACTS |
		    LDISC_RAW | LDISC_REMOTE);
		if (fd < 0) {
			msyslog(LOG_ERR, "acts: open fails %m");
			return;
		}
		pp->io.fd = fd;
		if (!io_addclock(&pp->io)) {
			msyslog(LOG_ERR, "acts: addclock fails");
			close(fd);
			pp->io.fd = -1;
			return;
		}
		up->msgcnt = 0;
		up->bufptr = up->buf;

		/*
		 * If the port is directly connected to the device, skip
		 * the modem business and send 'T' for Spectrabum.
		 */
		if (sys_phone[up->retry] == NULL) {
			if (write(pp->io.fd, "T", 1) < 0)
				msyslog(LOG_ERR, "acts: write T fails %m");
			up->state = S_MSG;
			up->timer = TIMECODE;
			return;
		}

		/*
		 * Initialize the modem. This works with Hayes-
		 * compatible modems.
		 */
		mprintf_event(PEVNT_CLOCK, peer, "SETUP %s",
			      modem_setup);
		rc = write(pp->io.fd, modem_setup, strlen(modem_setup));
		if (rc < 0)
			msyslog(LOG_ERR, "acts: write SETUP fails %m");
		write(pp->io.fd, "\r", 1);
		up->state = S_SETUP;
		up->timer = SETUP;
		return;

	/*
	 * In SETUP state the modem did not respond OK to setup string.
	 */
	case S_SETUP:
		report_event(PEVNT_CLOCK, peer, "no modem");
		break;

	/*
	 * In CONNECT state the call did not complete. Abort the call.
	 */
	case S_CONNECT:
		report_event(PEVNT_CLOCK, peer, "no answer");
		break;

	/*
	 * In MSG states no further timecodes are expected. If any
	 * timecodes have arrived, update the clock. In any case,
	 * terminate the call.
	 */
	case S_MSG:
		if (up->msgcnt == 0) {
			report_event(PEVNT_CLOCK, peer, "no timecodes");
		} else {
			pp->lastref = pp->lastrec;
			record_clock_stats(&peer->srcadr, pp->a_lastcode);
			refclock_receive(peer);
		}
		break;
	}
	acts_close(peer);
}


/*
 * acts_close - close and prepare for next call.
 *
 * In ClOSE state no further protocol actions are required
 * other than to close and release the device and prepare to
 * dial the next number if necessary.
 */
void
acts_close(
	struct peer *peer
	)
{
	struct actsunit *up;
	struct refclockproc *pp;
	char	lockfile[128];
	int	dtr;

	pp = peer->procptr;
	up = pp->unitptr;
	if (pp->io.fd != -1) {
		report_event(PEVNT_CLOCK, peer, "close");
		dtr = TIOCM_DTR;
		if (ioctl(pp->io.fd, TIOCMBIC, &dtr) < 0)
			msyslog(LOG_ERR, "acts: ioctl(TIOCMBIC) failed: %m");
		io_closeclock(&pp->io);
		pp->io.fd = -1;
	}
	if (pp->sloppyclockflag & CLK_FLAG2) {
		snprintf(lockfile, sizeof(lockfile),
		    LOCKFILE, up->unit);
		unlink(lockfile);
	}
	if (up->msgcnt == 0 && up->retry > 0) {
		if (sys_phone[up->retry] != NULL) {
			up->state = S_IDLE;
			up->timer = REDIAL;
			return;
		}
	}
	up->state = S_IDLE;
	up->timer = 0;
}


/*
 * acts_poll - called by the transmit routine
 */
static void
acts_poll(
	int	unit,
	struct peer *peer
	)
{
	struct actsunit *up;
	struct refclockproc *pp;

	/*
	 * This routine is called at every system poll. All it does is
	 * set flag1 under certain conditions. The real work is done by
	 * the timeout routine and state machine.
	 */
	pp = peer->procptr;
	up = pp->unitptr;
	switch (peer->ttl) {

	/*
	 * In manual mode the calling program is activated by the ntpdc
	 * program using the enable flag (fudge flag1), either manually
	 * or by a cron job.
	 */
	case MODE_MANUAL:
		return;

	/*
	 * In automatic mode the calling program runs continuously at
	 * intervals determined by the poll event or specified timeout.
	 */
	case MODE_AUTO:
		break;

	/*
	 * In backup mode the calling program runs continuously as long
	 * as either no peers are available or this peer is selected.
	 */
	case MODE_BACKUP:
		if (!(sys_peer == NULL || sys_peer == peer))
			return;

		break;
	}
	pp->polls++;
	if (S_IDLE == up->state) {
		up->retry = 0;
		acts_timeout(peer, S_IDLE);
	}
}


/*
 * acts_timer - called at one-second intervals
 */
static void
acts_timer(
	int	unit,
	struct peer *peer
	)
{
	struct actsunit *up;
	struct refclockproc *pp;

	/*
	 * This routine implments a timeout which runs for a programmed
	 * interval. The counter is initialized by the state machine and
	 * counts down to zero. Upon reaching zero, the state machine is
	 * called. If flag1 is set while timer is zero, force a call.
	 */
	pp = peer->procptr;
	up = pp->unitptr;
	if (up->timer == 0) {
		if (pp->sloppyclockflag & CLK_FLAG1) {
			pp->sloppyclockflag &= ~CLK_FLAG1;
			acts_timeout(peer, S_IDLE);
		}
	} else {
		up->timer--;
		if (up->timer == 0)
			acts_timeout(peer, up->state);
	}
}

/*
 * acts_timecode - identify the service and parse the timecode message
 */
void
acts_timecode(
	struct peer *	peer,	/* peer structure pointer */
	const char *	str	/* timecode string */
	)
{
	struct actsunit *up;
	struct refclockproc *pp;
	int	day;		/* day of the month */
	int	month;		/* month of the year */
	u_long	mjd;		/* Modified Julian Day */
	double	dut1;		/* DUT adjustment */

	u_int	dst;		/* ACTS daylight/standard time */
	u_int	leap;		/* ACTS leap indicator */
	double	msADV;		/* ACTS transmit advance (ms) */
	char	utc[10];	/* ACTS timescale */
	char	flag;		/* ACTS on-time character (* or #) */

	char	synchar;	/* WWVB synchronized indicator */
	char	qualchar;	/* WWVB quality indicator */
	char	leapchar;	/* WWVB leap indicator */
	char	dstchar;	/* WWVB daylight/savings indicator */
	int	tz;		/* WWVB timezone */

	int	leapmonth;	/* PTB/NPL month of leap */
	char	leapdir;	/* PTB/NPL leap direction */

	/*
	 * The parser selects the modem format based on the message
	 * length. Since the data are checked carefully, occasional
	 * errors due noise are forgivable.
	 */
	pp = peer->procptr;
	up = pp->unitptr;
	pp->nsec = 0;
	switch (strlen(str)) {

	/*
	 * For USNO format on-time character '*', which is on a line by
	 * itself. Be sure a timecode has been received.
	 */
	case 1:
		if (*str == '*' && up->msgcnt > 0) 
			break;

		return;
	
	/*
	 * ACTS format A: "jjjjj yy-mm-dd hh:mm:ss ds l uuu aaaaa
	 * UTC(NIST) *".
	 */
	case LENACTS:
		if (sscanf(str,
		    "%5ld %2d-%2d-%2d %2d:%2d:%2d %2d %1d %3lf %5lf %9s %c",
		    &mjd, &pp->year, &month, &day, &pp->hour,
		    &pp->minute, &pp->second, &dst, &leap, &dut1,
		    &msADV, utc, &flag) != 13) {
			refclock_report(peer, CEVNT_BADREPLY);
			return;
		}
		pp->day = ymd2yd(pp->year, month, day);
		pp->leap = LEAP_NOWARNING;
		if (leap == 1)
			pp->leap = LEAP_ADDSECOND;
		else if (leap == 2)
			pp->leap = LEAP_DELSECOND;
		memcpy(&pp->refid, REFACTS, 4);
		up->msgcnt++;
		if (flag != '#' && up->msgcnt < 10)
			return;

		break;

	/*
	 * USNO format: "jjjjj nnn hhmmss UTC"
	 */
	case LENUSNO:
		if (sscanf(str, "%5ld %3d %2d%2d%2d %3s",
		    &mjd, &pp->day, &pp->hour, &pp->minute,
		    &pp->second, utc) != 6) {
			refclock_report(peer, CEVNT_BADREPLY);
			return;
		}

		/*
		 * Wait for the on-time character, which follows in a
		 * separate message. There is no provision for leap
		 * warning.
		 */
		pp->leap = LEAP_NOWARNING;
		memcpy(&pp->refid, REFUSNO, 4);
		up->msgcnt++;
		break;

	/*
	 * PTB/NPL format: "yyyy-mm-dd hh:mm:ss MEZ" 
	 */
	case LENPTB:
		if (sscanf(str,
		    "%*4d-%*2d-%*2d %*2d:%*2d:%2d %*5c%*12c%4d%2d%2d%2d%2d%5ld%2lf%c%2d%3lf%*15c%c",
		    &pp->second, &pp->year, &month, &day, &pp->hour,
		    &pp->minute, &mjd, &dut1, &leapdir, &leapmonth,
		    &msADV, &flag) != 12) {
			refclock_report(peer, CEVNT_BADREPLY);
			return;
		}
		pp->leap = LEAP_NOWARNING;
		if (leapmonth == month) {
			if (leapdir == '+')
				pp->leap = LEAP_ADDSECOND;
			else if (leapdir == '-')
				pp->leap = LEAP_DELSECOND;
		}
		pp->day = ymd2yd(pp->year, month, day);
		memcpy(&pp->refid, REFPTB, 4);
		up->msgcnt++;
		break;


	/*
	 * WWVB format 0: "I  ddd hh:mm:ss DTZ=nn"
	 */
	case LENWWVB0:
		if (sscanf(str, "%c %3d %2d:%2d:%2d %cTZ=%2d",
		    &synchar, &pp->day, &pp->hour, &pp->minute,
		    &pp->second, &dstchar, &tz) != 7) {
			refclock_report(peer, CEVNT_BADREPLY);
			return;
		}
		pp->leap = LEAP_NOWARNING;
		if (synchar != ' ')
			pp->leap = LEAP_NOTINSYNC;
		memcpy(&pp->refid, REFWWVB, 4);
		up->msgcnt++;
		break;

	/*
	 * WWVB format 2: "IQyy ddd hh:mm:ss.mmm LD"
	 */
	case LENWWVB2:
		if (sscanf(str, "%c%c%2d %3d %2d:%2d:%2d.%3ld%c%c%c",
		    &synchar, &qualchar, &pp->year, &pp->day,
		    &pp->hour, &pp->minute, &pp->second, &pp->nsec,
		    &dstchar, &leapchar, &dstchar) != 11) {
			refclock_report(peer, CEVNT_BADREPLY);
			return;
		}
		pp->nsec *= 1000000;
		pp->leap = LEAP_NOWARNING;
		if (synchar != ' ')
			pp->leap = LEAP_NOTINSYNC;
		else if (leapchar == 'L')
			pp->leap = LEAP_ADDSECOND;
		memcpy(&pp->refid, REFWWVB, 4);
		up->msgcnt++;
		break;

	/*
	 * None of the above. Just forget about it and wait for the next
	 * message or timeout.
	 */
	default:
		return;
	}

	/*
	 * We have a valid timecode. The fudge time1 value is added to
	 * each sample by the main line routines. Note that in current
	 * telephone networks the propatation time can be different for
	 * each call and can reach 200 ms for some calls.
	 */
	peer->refid = pp->refid;
	pp->lastrec = up->tstamp;
	if (up->msgcnt == 0)
		return;

	strlcpy(pp->a_lastcode, str, sizeof(pp->a_lastcode));
	pp->lencode = strlen(pp->a_lastcode);
	if (!refclock_process(pp)) {
		refclock_report(peer, CEVNT_BADTIME);
		return;
	}
	pp->lastref = pp->lastrec;
}
#else
int refclock_acts_bs;
#endif /* REFCLOCK */